Compare commits

...

59 Commits

Author SHA1 Message Date
bc114b6ae0 Merge remote-tracking branch 'origin/Subscription_Plan' into Purchase_Invoice_Management 2025-12-16 11:06:57 +05:30
92a09bfcf0 Handle Date range in Expense Widget on dashboard
Link Project to detail page in Attendance Report Widget
2025-12-15 11:45:43 +05:30
4f9fa1b7c2 Add link to he project from Attendance details widget 2025-12-15 11:08:27 +05:30
34d5ce9ef9 missing request demo links 2025-12-14 17:22:21 +05:30
476fb49e07 adjust sidebar logo size and position from left border 2025-12-14 17:03:12 +05:30
7143af1e1e format DPR page 2025-12-14 16:49:06 +05:30
a477090cb8 Add missing images 2025-12-14 16:07:46 +05:30
f1a5f72db7 modified landing page hero images 2025-12-14 13:27:50 +05:30
e8886577d8 Correction in weidget. 2025-12-13 16:36:47 +05:30
e10a6ff14c Change the design of subscription page. 2025-12-13 14:51:17 +05:30
a309d13247 Correction in teams, projectinfra and daily task for dropdown. 2025-12-13 11:51:16 +05:30
fa694d8361 Correction in Attendance page. 2025-12-13 11:37:53 +05:30
a9bbd75d6c Correction in name of Project by status to Attendance by Project 2025-12-13 11:26:50 +05:30
3ff80ee032 Sorting in Subscription page. 2025-12-13 11:20:16 +05:30
e695807e77 Adding 2025-12-13 11:18:45 +05:30
630c11985d Correction in Subscription page. 2025-12-13 11:18:11 +05:30
d68cb9e664 Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Subscription_Plan 2025-12-13 09:59:35 +05:30
caeece0660 Merge branch 'main' of https://git.marcoaiot.com/admin/marco.pms.web into Purchase_Invoice_Management 2025-12-13 09:57:19 +05:30
44f3d8783d Adding additional fields in landing subscription cards. 2025-12-13 09:54:02 +05:30
3a8c1745f4 Removing dropdown logic all the places wehre dropdown is added. 2025-12-12 19:07:55 +05:30
deba5dfa01 Adding View functionality in Add challan. 2025-12-12 18:11:38 +05:30
bb743d2bb0 Adding Submit functionality in tenant. 2025-12-12 17:27:37 +05:30
6f9eeadc22 fixed project list paginantion 2025-12-12 16:22:55 +05:30
6099dd2ea5 fixed logo label on side bar 2025-12-12 15:25:21 +05:30
7b1b360c78 added missing view_all_employee permissions 2025-12-12 10:31:36 +05:30
cdaf642eba added zoom in - out using mouse wheel 2025-12-11 16:01:11 +05:30
b31195c5a1 Adding sorting in Project display. 2025-12-11 11:04:29 +05:30
9daeffd90e Adding formatcurrecny at hover effect at Expense Breakdown. 2025-12-11 10:54:24 +05:30
ab34ea63fa Merge branch 'Purchase_Invoice_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Purchase_Invoice_Management 2025-12-11 10:39:36 +05:30
17ef307ff2 Adding MyJobs at the place of all jobs in service Jobs. 2025-12-11 10:39:32 +05:30
ae9ef7938d fixed : Project created permission for service 2025-12-11 10:37:39 +05:30
e755faecdc fixed: added array guards in mergedServices to stop iterable error 2025-12-11 10:25:45 +05:30
23660379c9 fixed 'ProjectwiseTeamCount' path inside dashboard 2025-12-11 09:51:01 +05:30
2bf5e9a13f Merge branch 'Encrypted_Reponse' into Purchase_Invoice_Management 2025-12-11 09:40:09 +05:30
3413806670 Merge branch 'Encrypted_Reponse' into Purchase_Invoice_Management 2025-12-11 09:39:51 +05:30
8033fdb7e7 selected default current selected service - create task 2025-12-10 16:52:52 +05:30
b62fc82a9c Merge branch 'Purchase_Invoice_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Purchase_Invoice_Management 2025-12-10 16:33:04 +05:30
4253ed71eb modified ui base on suggestion 2025-12-10 16:32:59 +05:30
01fc302011 Merge pull request 'Adding_Chips_DailyTask :- Adding Chips in Daily task report.' (#538) from Adding_Chips_DailyTask into Purchase_Invoice_Management
Reviewed-on: #538
merged
2025-12-10 11:01:16 +00:00
d56aefde02 Adding sizing in manageemployee. 2025-12-10 11:01:16 +00:00
c71fe3a45e Adding dates chips in document. 2025-12-10 11:01:16 +00:00
0f43c877c4 Adding Date picker chips in Expense and 2025-12-10 11:01:16 +00:00
153ffcdc3e Adding w-100 at doument filter panel. 2025-12-10 11:01:16 +00:00
e31e4cfc31 Adding Filter chips at Tenant. 2025-12-10 11:01:16 +00:00
8216bf1f2d Correction in Infra Project Card view. 2025-12-10 11:01:16 +00:00
6b9d7c56bc Adding filter chips in daily Progress Report. 2025-12-10 11:01:16 +00:00
07ba95e533 removed console warning 2025-12-10 12:40:30 +05:30
c1ae9ee55e changed footer 2025-12-10 12:26:43 +05:30
117d82769a Merge branch 'Purchase_Invoice_Management' of https://git.marcoaiot.com/admin/marco.pms.web into dpr-report 2025-12-10 12:16:11 +05:30
35f1aa8c13 hide attendance project wise overview project having selected 2025-12-10 12:12:12 +05:30
cd1ae64753 added attandace poject wise overview 2025-12-10 12:09:53 +05:30
363a9c5feb Merge branch 'Purchase_Invoice_Management' of https://git.marcoaiot.com/admin/marco.pms.web into Purchase_Invoice_Management 2025-12-10 10:32:00 +05:30
69148331f5 added partially proejctwise attendace 2025-12-10 10:31:54 +05:30
65158b9368 added encryption 2025-12-09 16:37:13 +05:30
96bcdffdca added optional chain for n=menu 2025-12-05 18:45:19 +05:30
b348117f05 added Total employee present figure 2025-11-25 09:50:39 +05:30
1157643916 added report data by project and date wise 2025-11-24 16:08:45 +05:30
37212e489e splitted code and added server data 2025-11-22 16:38:39 +05:30
b2c0388412 dpr report wireframe 2025-11-22 13:14:57 +05:30
74 changed files with 2471 additions and 962 deletions

7
package-lock.json generated
View File

@ -18,6 +18,7 @@
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",
@ -2414,6 +2415,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/crypto-js": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",

View File

@ -21,6 +21,7 @@
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"axios-retry": "^4.5.0", "axios-retry": "^4.5.0",
"crypto-js": "^4.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 500 KiB

After

Width:  |  Height:  |  Size: 52 KiB

BIN
public/img/hero/bg-011.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -0,0 +1,95 @@
import React, { useMemo } from "react";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const TaskReportFilterChips = ({ filter, filterData, removeFilterChip, clearFilter }) => {
const data = filterData?.data || filterData || {};
const filterChips = useMemo(() => {
const chips = [];
const addGroup = (ids, list, label, key) => {
if (!ids || ids.length === 0) return;
const items = ids.map((id) => ({
id,
name: list?.find((i) => i.id === id)?.name || id,
}));
chips.push({ key, label, items });
};
// Building
addGroup(filter?.buildingIds, data?.buildings, "Building", "buildingIds");
// Floor
addGroup(filter?.floorIds, data?.floors, "Floor", "floorIds");
// Activities
addGroup(filter?.activityIds, data?.activities, "Activity", "activityIds");
// Date Range Chips
if (filter?.dateFrom || filter?.dateTo) {
chips.push({
key: "date",
label: "Date Range",
items: [
{
id: "date-range",
name: `${filter?.dateFrom ? formatUTCToLocalTime(filter.dateFrom) : ""}
${filter?.dateTo ? " to " + formatUTCToLocalTime(filter.dateTo) : ""}`,
},
],
});
}
return chips;
}, [filter, filterData]);
if (!filterChips.length) return null;
return (
<div className="d-flex flex-wrap align-items-center gap-2 mt-2">
{filterChips.map((chipGroup) => (
<div key={chipGroup.key} className="d-flex align-items-center flex-wrap">
<span className="fw-semibold me-2">{chipGroup.label}:</span>
{chipGroup.items.map((item) => (
<span
key={item.id}
className="d-flex align-items-center bg-light rounded px-2 py-1 me-1"
>
<span>{item.name}</span>
{/* If date chip → remove whole date range */}
{chipGroup.key === "date" ? (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => clearFilter()}
/>
) : (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => removeFilterChip(chipGroup.key, item.id)}
/>
)}
</span>
))}
</div>
))}
</div>
);
};
export default TaskReportFilterChips;

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from "react";
import { useCurrentService } from "../../hooks/useProjects"; import { useCurrentService } from "../../hooks/useProjects";
import { useSelectedProject } from "../../slices/apiDataManager"; import { useSelectedProject } from "../../slices/apiDataManager";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
@ -11,8 +11,9 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
import SelectMultiple from "../common/SelectMultiple"; import SelectMultiple from "../common/SelectMultiple";
import { localToUtc } from "../../utils/appUtils"; import { localToUtc } from "../../utils/appUtils";
import { useTaskFilter } from "../../hooks/useTasks"; import { useTaskFilter } from "../../hooks/useTasks";
import { set } from "date-fns";
const TaskReportFilterPanel = ({ handleFilter }) => { const TaskReportFilterPanel = forwardRef(({ handleFilter, setFilterdata, clearFilter }, ref) => {
const [resetKey, setResetKey] = useState(0); const [resetKey, setResetKey] = useState(0);
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const selectedService = useCurrentService(); const selectedService = useCurrentService();
@ -23,10 +24,42 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
defaultValues: TaskReportDefaultValue, defaultValues: TaskReportDefaultValue,
}); });
const dynamicDefaultFilter = useMemo(() => {
return {
...TaskReportDefaultValue,
buildingIds: TaskReportDefaultValue.buildingIds || [],
floorIds: TaskReportDefaultValue.floorIds || [],
activityIds: TaskReportDefaultValue.activityIds || [],
dateFrom: TaskReportDefaultValue.dateFrom,
dateTo: TaskReportDefaultValue.dateTo,
};
}, [selectedProject]);
useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
// Reset specific field
if (value !== undefined) {
setValue(name, value);
} else {
// Fix: Use TaskReportDefaultValue or get current values before setting new ones
// reset({ ...methods.getValues(), [name]: TaskReportDefaultValue[name] }); // Updated to use TaskReportDefaultValue
setValue(name, TaskReportDefaultValue[name]);
}
},
getValues: methods.getValues,
onClear: onClear, // 💡 EXPOSE THE ONCLEAR FUNCTION
}));
useEffect(() => {
if (data && setFilterdata) {
setFilterdata(data);
}
}, [data, setFilterdata]);
const { const {
register, register,
reset, reset,
handleSubmit, handleSubmit,
setValue,
formState: { errors }, formState: { errors },
} = methods; } = methods;
const closePanel = () => { const closePanel = () => {
@ -52,7 +85,7 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
<div className="mb-3 w-100"> <div className="mb-3 w-100">
<label className="fw-semibold">Choose Date Range:</label> <label className="fw-semibold">Choose Date Range:</label>
<DateRangePicker1 <DateRangePicker1
className="w-100" className="w-100"
placeholder="DD-MM-YYYY To DD-MM-YYYY" placeholder="DD-MM-YYYY To DD-MM-YYYY"
startField="dateFrom" startField="dateFrom"
endField="dateTo" endField="dateTo"
@ -105,6 +138,6 @@ const TaskReportFilterPanel = ({ handleFilter }) => {
</form> </form>
</FormProvider> </FormProvider>
); );
}; });
export default TaskReportFilterPanel; export default TaskReportFilterPanel;

View File

@ -17,8 +17,9 @@ import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import Pagination from "../common/Pagination"; import Pagination from "../common/Pagination";
import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton"; import { TaskReportListSkeleton } from "./TaskRepprtListSkeleton";
import HoverPopup from "../common/HoverPopup"; import HoverPopup from "../common/HoverPopup";
import TaskReportFilterChips from "./TaskReportFilterChips";
const TaskReportList = () => { const TaskReportList = ({ filter, filterData, removeFilterChip, clearFilter }) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState({ const [filters, setFilters] = useState({
selectedBuilding: "", selectedBuilding: "",
@ -29,7 +30,7 @@ const TaskReportList = () => {
const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK); const ApprovedTaskRights = useHasUserPermission(APPROVE_TASK);
const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK); const ReportTaskRights = useHasUserPermission(ASSIGN_REPORT_TASK);
const { service, openModal, closeModal, filter } = useDailyProgrssContext(); const { service, openModal, closeModal, filter: contextFilter } = useDailyProgrssContext();
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const { projectNames } = useProjectName(); const { projectNames } = useProjectName();
@ -37,7 +38,7 @@ const TaskReportList = () => {
selectedProject, selectedProject,
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
currentPage, currentPage,
service, filter service, contextFilter
); );
const ProgrssReportColumn = [ const ProgrssReportColumn = [
@ -193,124 +194,135 @@ const TaskReportList = () => {
if (isError) return <div>Loading....</div>; if (isError) return <div>Loading....</div>;
return ( return (
<div> <div>
<div className="mt-2 table-responsive text-nowrap">
<table className="table">
<thead>
<tr>
<th className="text-start">Activity</th>
<th>
<span>
Total Pending{" "}
<HoverPopup
id="total_pending_task"
title="Total Pending Task"
content={
<div className="text-wrap" style={{ minWidth: "200px" }}>
This shows the total pending tasks for each activity on that date.
</div>
}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th> <div className="main-content">
<span> <div className="col-12 mb-2 mt-2 px-4">
Reported/Planned{" "} <TaskReportFilterChips
<HoverPopup filter={filter}
id="reportes_and_planned_task" filterData={filterData}
title="Reported and Planned Task" removeFilterChip={removeFilterChip}
content={ clearFilter={clearFilter}
<div className="text-wrap" style={{ maxWidth: "200px" }}> />
This shows the reported versus planned tasks for each activity on that date. </div>
</div> <div className="mt-2 table-responsive text-nowrap">
} <table className="table">
> <thead>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>Assign Date</th>
<th>Team</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
{groupedTasks.length === 0 && (
<tr> <tr>
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}> <th className="text-start">Activity</th>
No reports available <th>
</td> <span>
</tr> Total Pending{" "}
)} <HoverPopup
id="total_pending_task"
title="Total Pending Task"
content={
<div className="text-wrap" style={{ minWidth: "200px" }}>
This shows the total pending tasks for each activity on that date.
</div>
}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
{groupedTasks.map(({ date, tasks }) => ( <th>
<React.Fragment key={date}> <span>
<tr className="table-row-header text-start"> Reported/Planned{" "}
<td colSpan={6}> <HoverPopup
<strong>{formatUTCToLocalTime(date)}</strong> id="reportes_and_planned_task"
title="Reported and Planned Task"
content={
<div className="text-wrap" style={{ maxWidth: "200px" }}>
This shows the reported versus planned tasks for each activity on that date.
</div>
}
>
<i className="bx bx-xs ms-1 bx-info-circle cursor-pointer"></i>
</HoverPopup>
</span>
</th>
<th>Assign Date</th>
<th>Team</th>
<th className="text-center">Actions</th>
</tr>
</thead>
<tbody>
{groupedTasks.length === 0 && (
<tr>
<td colSpan={6} className="text-center align-middle" style={{ height: "200px", borderBottom: "none" }}>
No reports available
</td> </td>
</tr> </tr>
{tasks.map((task, idx) => ( )}
<tr key={task.id || idx}>
<td className="flex-wrap text-start"> {groupedTasks.map(({ date, tasks }) => (
<div> <React.Fragment key={date}>
{task.workItem.activityMaster?.activityName || "No Activity Name"} <tr className="table-row-header text-start">
</div> <td colSpan={6}>
<div className="text-sm py-2"> <strong>{formatUTCToLocalTime(date)}</strong>
{task.workItem.workArea?.floor?.building?.name} {" "}
{task.workItem.workArea?.floor?.floorName} {" "}
{task.workItem.workArea?.areaName}
</div>
</td>
<td>
{formatNumber(task.workItem.plannedWork)}
</td>
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
<td className="text-center">{renderTeamMembers(task, idx)}</td>
<td className="text-center">
<div className="d-flex justify-content-end gap-2">
{ReportTaskRights && !task.reportedDate && (
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
Report
</button>
)}
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
<button
className="btn btn-xs btn-warning"
onClick={() => openModal("comments", { task, isActionAllow: true })}
>
QC
</button>
)}
<button
className="btn btn-xs btn-primary"
onClick={() => openModal("comments", { task, isActionAllow: false })}
>
Comment
</button>
</div>
</td> </td>
</tr> </tr>
))} {tasks.map((task, idx) => (
</React.Fragment> <tr key={task.id || idx}>
))} <td className="flex-wrap text-start">
</tbody> <div>
</table> {task.workItem.activityMaster?.activityName || "No Activity Name"}
</div>
<div className="text-sm py-2">
{task.workItem.workArea?.floor?.building?.name} {" "}
{task.workItem.workArea?.floor?.floorName} {" "}
{task.workItem.workArea?.areaName}
</div>
</td>
<td>
{formatNumber(task.workItem.plannedWork)}
</td>
<td>{`${formatNumber(task.completedTask)} / ${formatNumber(task.plannedTask)}`}</td>
<td>{formatUTCToLocalTime(task.assignmentDate)}</td>
<td className="text-center">{renderTeamMembers(task, idx)}</td>
<td className="text-center">
<div className="d-flex justify-content-end gap-2">
{ReportTaskRights && !task.reportedDate && (
<button className="btn btn-xs btn-primary" onClick={() => openModal("report", task)}>
Report
</button>
)}
{ApprovedTaskRights && task.reportedDate && !task.approvedBy && (
<button
className="btn btn-xs btn-warning"
onClick={() => openModal("comments", { task, isActionAllow: true })}
>
QC
</button>
)}
<button
className="btn btn-xs btn-primary"
onClick={() => openModal("comments", { task, isActionAllow: false })}
>
Comment
</button>
</div>
</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div> </div>
{ {
data?.data?.length > 0 && ( data?.data?.length > 0 && (
<Pagination <Pagination
currentPage={currentPage} currentPage={currentPage}
totalPages={data.totalPages} totalPages={data.totalPages}
onPageChange={paginate} onPageChange={paginate}
/> />
) )
} }
</div >
</div > </div >
); );
}; };

View File

@ -31,7 +31,7 @@ const selectedProjectId = useSelectedProject()
<div className="card-header mb-1 pb-0"> <div className="card-header mb-1 pb-0">
<div className="d-flex flex-wrap justify-content-between align-items-center"> <div className="d-flex flex-wrap justify-content-between align-items-center">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
<h5 className="mb-1">Attendance</h5> <h5 class="card-title m-0 me-2">Attendance</h5>
<p className="card-subtitle">Daily Attendance Data</p> <p className="card-subtitle">Daily Attendance Data</p>
</div> </div>

View File

@ -104,7 +104,7 @@ const AttendanceOverview = () => {
{/* Header */} {/* Header */}
<div className="d-flex mt-2 justify-content-between align-items-center mb-3"> <div className="d-flex mt-2 justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
<h5 className="mb-1 fw-bold">Attendance Overview</h5> <h5 className="mb-1 fw-semibold">Attendance Overview</h5>
<p className="card-subtitle">Role-wise present count</p> <p className="card-subtitle">Role-wise present count</p>
</div> </div>
<div className="d-flex gap-2"> <div className="d-flex gap-2">

View File

@ -78,7 +78,7 @@ const CollectionOverview = ({ data, isLoading }) => {
tooltip: { tooltip: {
custom: ({ series, seriesIndex, dataPointIndex }) => { custom: ({ series, seriesIndex, dataPointIndex }) => {
return ` return `
<div class="px-2 py-1"> <div className="px-2 py-1">
<strong>${labels[dataPointIndex]}</strong><br> <strong>${labels[dataPointIndex]}</strong><br>
${series[seriesIndex][dataPointIndex].toLocaleString()} ${series[seriesIndex][dataPointIndex].toLocaleString()}
</div> </div>
@ -235,7 +235,7 @@ export const TopicBarChart = ({ data,isLoading }) => {
enabled: true, enabled: true,
custom: ({ series, seriesIndex, dataPointIndex }) => { custom: ({ series, seriesIndex, dataPointIndex }) => {
return ` return `
<div class="px-3 py-2"> <div className="px-3 py-2">
<span>${series[seriesIndex][ <span>${series[seriesIndex][
dataPointIndex dataPointIndex
].toLocaleString()}</span> ].toLocaleString()}</span>
@ -256,8 +256,8 @@ export const TopicBarChart = ({ data,isLoading }) => {
return ( return (
<div className="row p-2"> <div className="row p-2">
<div className="col-md-8"> <div className="col-md-8">
<div class="card-header d-flex align-items-center justify-content-between"> <div className="card-header d-flex align-items-center justify-content-between">
<h5 class="card-title m-0 me-2">Collection Overview</h5> <h5 className="card-title m-0 me-2">Collection Overview</h5>
</div> </div>
<div className="w-100 d-flex align-items-center text-start px-6"> <div className="w-100 d-flex align-items-center text-start px-6">
<p className="text-secondary fs-6 m-0">Due Amount</p> <p className="text-secondary fs-6 m-0">Due Amount</p>
@ -274,7 +274,7 @@ export const TopicBarChart = ({ data,isLoading }) => {
</div> </div>
<div className="col-md-4 d-flex flex-column gap-2"> <div className="col-md-4 d-flex flex-column gap-2">
<div class="card-header d-flex align-items-end justify-content-between"></div> <div className="card-header d-flex align-items-end justify-content-between"></div>
<div className="p-1 m-1 text-start"> <div className="p-1 m-1 text-start">
<small className="fw-medium">Overdue Days</small> <small className="fw-medium">Overdue Days</small>
</div> </div>

View File

@ -27,6 +27,8 @@ import {
} from "../../utils/constants"; } from "../../utils/constants";
import CollectionOverview, { TopicBarChart } from "./CollectionOverview"; import CollectionOverview, { TopicBarChart } from "./CollectionOverview";
import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton"; import { CollectionOverviewSkeleton } from "./CollectionOverviewSkeleton";
import ProjectWiseTeamCount from "./ProjectWiseTeamCount";
const Dashboard = () => { const Dashboard = () => {
// Get the selected project ID from Redux store // Get the selected project ID from Redux store
@ -41,24 +43,24 @@ const Dashboard = () => {
<div className="container-fluid mt-5"> <div className="container-fluid mt-5">
<div className="row gy-4"> <div className="row gy-4">
{isAllProjectsSelected && ( {isAllProjectsSelected && (
<div className="col-sm-6 col-lg-4"> <div className="col-sm-6 col-lg-6">
<Projects /> <Projects />
</div> </div>
)} )}
<div <div
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4" className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-6"
}`} }`}
> >
<Teams /> <Teams />
</div> </div>
<div {!isAllProjectsSelected && ( <div
className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-4" className={`${!isAllProjectsSelected ? "col-sm-6 col-lg-6" : "col-sm-6 col-lg-6"
}`} }`}
> >
<TasksCard /> <TasksCard />
</div> </div>)}
<div className="col-12 col-xl-4 col-md-6"> <div className="col-12 col-xl-4 col-md-6">
<div className="card "> <div className="card ">
<ExpenseStatus /> <ExpenseStatus />
@ -84,10 +86,13 @@ const Dashboard = () => {
</div> </div>
{!isAllProjectsSelected && {!isAllProjectsSelected &&
(canRegularize || canTeamAttendance || canSelfAttendance) && ( (canRegularize || canTeamAttendance || canSelfAttendance) && (
<div className="col-12 col-md-8 mb-sm-0 mb-4"> <div className="col-12 col-md-6 mb-sm-0 mb-4">
<AttendanceOverview /> <AttendanceOverview />
</div> </div>
)} )}
{isAllProjectsSelected && <div className="col-12 col-md-6 mb-sm-0 mb-4">
<ProjectWiseTeamCount />
</div>}
{!isAllProjectsSelected && ( {!isAllProjectsSelected && (
<div className="col-xxl-4 col-lg-4"> <div className="col-xxl-4 col-lg-4">
@ -97,7 +102,7 @@ const Dashboard = () => {
</div> </div>
)} )}
{isAllProjectsSelected && ( {isAllProjectsSelected && (
<div className="col-12 col-md-6 mb-sm-0 mb-4"> <div className="col-12 col-md-4 mb-sm-0 mb-4">
<ServiceJobs /> <ServiceJobs />
</div> </div>
)} )}
@ -118,80 +123,3 @@ const Dashboard = () => {
}; };
export default Dashboard; export default Dashboard;
// <div class="col-12 col-xl-8">
// <div class="card h-100">
// <div class="card-header d-flex align-items-center justify-content-between">
// <h5 class="card-title m-0 me-2">Topic you are interested in</h5>
// <div class="dropdown">
// <button
// class="btn p-0"
// type="button"
// id="topic"
// data-bs-toggle="dropdown"
// aria-haspopup="true"
// aria-expanded="false">
// <i class="bx bx-dots-vertical-rounded bx-lg text-muted"></i>
// </button>
// <div class="dropdown-menu dropdown-menu-end" aria-labelledby="topic">
// <a class="dropdown-item" href="javascript:void(0);">Highest Views</a>
// <a class="dropdown-item" href="javascript:void(0);">See All</a>
// </div>
// </div>
// </div>
// <div class="card-body row g-3">
// <div class="col-md-8">
// <div id="horizontalBarChart"></div>
// </div>
// <div class="col-md-4 d-flex justify-content-around align-items-center">
// <div>
// <div class="d-flex align-items-baseline">
// <span class="text-primary me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">UI Design</p>
// <h5>35%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline my-12">
// <span class="text-success me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">Music</p>
// <h5>14%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline">
// <span class="text-danger me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">React</p>
// <h5>10%</h5>
// </div>
// </div>
// </div>
// <div>
// <div class="d-flex align-items-baseline">
// <span class="text-info me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">UX Design</p>
// <h5>20%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline my-12">
// <span class="text-secondary me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">Animation</p>
// <h5>12%</h5>
// </div>
// </div>
// <div class="d-flex align-items-baseline">
// <span class="text-warning me-2"><i class="bx bxs-circle bx-12px"></i></span>
// <div>
// <p class="mb-0">SEO</p>
// <h5>9%</h5>
// </div>
// </div>
// </div>
// </div>
// </div>
// </div>
// </div>

View File

@ -52,6 +52,18 @@ const ExpenseAnalysis = () => {
legend: { show: false }, legend: { show: false },
dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` }, dataLabels: { enabled: true, formatter: (val) => `${val.toFixed(0)}%` },
colors: flatColors, colors: flatColors,
tooltip: {
y: {
formatter: function (value) {
return formatCurrency(value);
},
},
x: {
formatter: function (label) {
return label;
},
},
},
plotOptions: { plotOptions: {
pie: { pie: {
donut: { donut: {
@ -88,7 +100,7 @@ const ExpenseAnalysis = () => {
<div className="text-end text-sm-end"> <div className="text-end text-sm-end">
<FormProvider {...methods}> <FormProvider {...methods}>
<DateRangePicker1 /> <DateRangePicker1 pastDays="30" />
</FormProvider> </FormProvider>
</div> </div>
</div> </div>
@ -140,7 +152,9 @@ const ExpenseAnalysis = () => {
className="col-6" className="col-6"
key={idx} key={idx}
style={{ style={{
borderLeft: `3px solid ${flatColors[idx % flatColors.length]}`, borderLeft: `3px solid ${
flatColors[idx % flatColors.length]
}`,
}} }}
> >
<div className="d-flex flex-column text-start"> <div className="d-flex flex-column text-start">
@ -165,7 +179,6 @@ const ExpenseAnalysis = () => {
</span> </span>
</div> </div>
</div> </div>
))} ))}
</div> </div>
</div> </div>

View File

@ -92,45 +92,40 @@ const ExpenseByProject = () => {
<div className="card shadow-sm h-100 rounded "> <div className="card shadow-sm h-100 rounded ">
{/* Header */} {/* Header */}
<div className="card-header"> <div className="card-header">
<div className="d-flex justify-content-between align-items-center mb-1 mt-1"> <div className="d-block justify-content-between align-items-center mb-1 mt-1">
<div className="text-start"> <div className="text-start">
<h5 className="mb-1 me-6 card-title">Monthly Expense -</h5> <h5 className="mb-1 me-6 card-title">Monthly Expense </h5>
<p className="card-subtitle m-0">{projectName}</p> <div className="row w-100">
</div> <div className="col-6"> <p className="card-subtitle m-0">{projectName}</p></div>
<div className="btn-group mb-5 ms-n8"> <div className="col-6 d-flex justify-content-between align-items-center px-0">
<button
className="btn btn-sm dropdown-toggle fs-5"
type="button"
data-bs-toggle="dropdown" <select
aria-expanded="false" className="form-select form-select-sm ms-auto mt-sm-0"
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
disabled={typeLoading}
style={{ maxWidth: "200px" }}
> >
{viewMode} <option value="">All Categories</option>
</button> {expenseCategories?.map((type) => (
<ul className="dropdown-menu dropdown-menu-end "> <option key={type.id} value={type.id}>
<li> {type.name}
<button </option>
className="dropdown-item" ))}
onClick={() => { </select>
setViewMode("Category");
setSelectedType(""); </div>
}}
>
Category
</button>
</li> </div>
<li>
<button
className="dropdown-item"
onClick={() => {
setViewMode("Project");
setSelectedType("");
}}
>
Project
</button>
</li>
</ul>
</div> </div>
</div> </div>
{/* Range Buttons + Expense Dropdown */} {/* Range Buttons + Expense Dropdown */}
@ -148,22 +143,7 @@ const ExpenseByProject = () => {
{item} {item}
</button> </button>
))} ))}
{viewMode === "Category" && (
<select
className="form-select form-select-sm ms-auto mb-3 mt-1 mt-sm-0"
value={selectedType}
onChange={(e) => setSelectedType(e.target.value)}
disabled={typeLoading}
style={{ maxWidth: "200px" }}
>
<option value="">All Types</option>
{expenseCategories?.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
)}
</div> </div>
</div> </div>

View File

@ -92,7 +92,7 @@ const ProjectProgressChart = ({
<div className="d-flex flex-wrap justify-content-between align-items-start mb-2"> <div className="d-flex flex-wrap justify-content-between align-items-start mb-2">
{/* Left: Title */} {/* Left: Title */}
<div className="card-title text-start"> <div className="card-title text-start">
<h5 className="mb-1 fw-bold">Project Progress</h5> <h5 className="mb-1 fw-semibold">Project Progress</h5>
<p className="card-subtitle">Progress Overview by Project</p> <p className="card-subtitle">Progress Overview by Project</p>
</div> </div>
</div> </div>

View File

@ -0,0 +1,112 @@
import React from "react";
import { useProjectName } from "../../hooks/useProjects";
import { BUCKET_BG_CLASSES } from "../../utils/constants";
import { useAttendaceProjectWiseOveriew } from "../../hooks/useDashboard_Data";
import { AppColors, localToUtc } from "../../utils/appUtils";
import DatePicker from "../common/DatePicker";
import { useAppForm, useAppWatch } from "../../hooks/appHooks/useAppForm";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { setProjectId } from "../../slices/localVariablesSlice";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
const ProjectWiseTeamCount = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { control } = useAppForm({
resolver: zodResolver(
z.object({
date: z.string().optional(),
})
),
defaultValues: {
date: new Date().toISOString().slice(0, 10),
},
});
const goToProject = (projectId) => () => {
dispatch(setProjectId(projectId));
navigate(`/projects/details`);
};
const selectedDate = useAppWatch({ control, name: "date" });
const { data, isLoading, isFetching, isError, error } =
useAttendaceProjectWiseOveriew(localToUtc(selectedDate));
const percent = (teamCount, attendanceCount) => {
return teamCount > 0 ? Math.round((attendanceCount / teamCount) * 100) : 0;
};
return (
<div className="card h-100 p-3">
{/* Header */}
<div className="d-flex justify-content-between text-start mb-2">
<h5 className="card-title m-0 me-2">Attendance by Project</h5>
<DatePicker name="date" control={control} maxDate={new Date()} />
</div>
{/* Only show spinner for new data, not full component */}
{isFetching && !isLoading && (
<div className="small text-end text-muted">Updating</div>
)}
{/* Table */}
<div className="table-container">
<table className="table table-borderless mb-0">
<thead className="table-header">
<tr>
<th style={{ width: 200 }} className="text-start">
Project
</th>
<th style={{ width: 100 }}>Team Size</th>
<th style={{ width: 80 }} className="text-start">
Logged In
</th>
{/* <th>Percentage</th> */}
</tr>
</thead>
</table>
<div
className="table-body-scroll overflow-auto pe-1"
style={{ maxHeight: "60vh" }}
>
<table className="table table-borderless mb-0">
<tbody>
{(data ?? []).map((item, index) => (
<tr key={item.projectId || index}>
<td style={{ width: 200 }}>
<div
className="d-flex align-items-center text-wrap my-2 text-start"
style={{ width: "180px" }}
>
<a
onClick={goToProject(item.projectId)}
className="text-heading text-truncate cursor-pointer"
>
{" "}
<span className="text-heading">{item.projectName}</span>
</a>
</div>
</td>
<td className="text-center" style={{ width: 80 }}>
{item.teamCount}
</td>
<td className="text-center" style={{ width: 80 }}>
{item.attendanceCount}
</td>
{/* <td>{percent(item.teamCount, item.attendanceCount)}%</td> */}
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default ProjectWiseTeamCount;

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { useEffect, useRef, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useJobsProgression } from "../../hooks/useDashboard_Data"; import { useJobsProgression } from "../../hooks/useDashboard_Data";
import { SpinnerLoader } from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
@ -7,56 +7,90 @@ import { useServiceProject } from "../../hooks/useServiceProject";
const ServiceJobs = () => { const ServiceJobs = () => {
const { projectId } = useParams(); const { projectId } = useParams();
const { data, isLoading, isError } = useJobsProgression(projectId);
const { data, isLoading, isError } = useJobsProgression(projectId);
const jobs = data || {}; const jobs = data || {};
const { data: projectData, isLoading: projectLoading } = useServiceProject(projectId);
const { data: projectData, isLoading: projectLoading } =
useServiceProject(projectId);
const [activeTab, setActiveTab] = useState("tab-new");
// 👇 prevents re-running auto logic after first load
const hasInitializedTab = useRef(false);
const tabMapping = [ const tabMapping = [
{ id: "tab-new", label: "My Jobs", key: "allJobs" }, { id: "tab-new", label: "My Jobs", key: "myJobs" },
{ id: "tab-preparing", label: "Assigned", key: "assignedJobs" },
{ id: "tab-shipping", label: "In Progress", key: "inProgressJobs" }, { id: "tab-shipping", label: "In Progress", key: "inProgressJobs" },
{ id: "tab-preparing", label: "Assigned", key: "assignedJobs" },
]; ];
/* ---------- INITIAL TAB SELECTION ONLY ---------- */
useEffect(() => {
if (hasInitializedTab.current || !jobs) return;
if (jobs.myJobs?.length > 0) {
setActiveTab("tab-new");
} else if (jobs.inProgressJobs?.length > 0) {
setActiveTab("tab-shipping");
} else {
setActiveTab("tab-preparing");
}
hasInitializedTab.current = true;
}, [jobs]);
return ( return (
<div className=""> <div>
<div className="card page-min-h"> <div className="card page-min-h">
{/* Header */}
<div className="card-header d-flex justify-content-between"> <div className="card-header d-flex justify-content-between">
<div className="card-title mb-0 text-start"> <div className="card-title mb-0 text-start">
<h5 className="mb-1 fw-bold">Service Jobs</h5> <h5 className="mb-1 fw-bold">Service Jobs</h5>
<p className="card-subtitle"> <p className="card-subtitle">
{projectLoading ? "Loading..." : projectData?.name || "All Projects"} {projectLoading
? "Loading..."
: projectData?.name || "All Projects"}
</p> </p>
</div> </div>
</div> </div>
<div className="card-body p-0"> <div className="card-body p-0">
<div className="nav-align-top"> <div className="nav-align-top">
{/* Tabs */}
{/* ---------------- Tabs ---------------- */} <ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced">
<ul className="nav nav-tabs nav-fill rounded-0 timeline-indicator-advanced" role="tablist"> {tabMapping.map((tab) => (
{tabMapping.map((t, index) => ( <li className="nav-item" key={tab.id}>
<li className="nav-item" key={t.id}>
<button <button
className={`nav-link ${index === 0 ? "active" : ""}`} type="button"
data-bs-toggle="tab" className={`nav-link ${
data-bs-target={`#${t.id}`} activeTab === tab.id ? "active" : ""
}`}
onClick={() => setActiveTab(tab.id)}
> >
{t.label} {tab.label}
</button> </button>
</li> </li>
))} ))}
</ul> </ul>
{/* ---------------- Tab Content ---------------- */} {/* Content */}
<div className="tab-content border-0 mx-1 text-start"> <div className="tab-content border-0 mx-1 text-start">
{isLoading && ( {isLoading && (
<div className="text-center" style={{ height: "250px", display: "flex", justifyContent: "center", alignItems: "center" }}> <div
className="text-center"
style={{
height: "250px",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
<SpinnerLoader /> <SpinnerLoader />
</div> </div>
)} )}
{isError && ( {isError && (
<p <p
className="text-center" className="text-center"
@ -70,19 +104,19 @@ const ServiceJobs = () => {
> >
No data found No data found
</p> </p>
)} )}
{!isLoading && {!isLoading &&
!isError && !isError &&
tabMapping.map((t, index) => { tabMapping.map((tab) => {
const list = jobs[t.key] || []; const list = jobs[tab.key] || [];
return ( return (
<div <div
key={t.id} key={tab.id}
className={`tab-pane fade ${index === 0 ? "show active" : ""}`} className={`tab-pane fade ${
id={t.id} activeTab === tab.id ? "show active" : ""
}`}
> >
{list.length === 0 ? ( {list.length === 0 ? (
<p <p
@ -97,24 +131,19 @@ const ServiceJobs = () => {
> >
No jobs found No jobs found
</p> </p>
) : ( ) : (
<div className="job-scroll-wrapper"> <div className="job-scroll-wrapper">
{list.map((job, i) => ( {list.map((job, index) => (
<React.Fragment key={i}> <React.Fragment key={index}>
<ul className="timeline mb-0"> <ul className="timeline mb-0">
{/* Assigned By */}
<li className="timeline-item ps-6 border-left-dashed"> <li className="timeline-item ps-6 border-left-dashed">
<span className="timeline-indicator-advanced timeline-indicator-success border-0 shadow-none"> <span className="timeline-indicator-advanced timeline-indicator-success border-0 shadow-none">
<i className="bx bx-check-circle"></i> <i className="bx bx-check-circle"></i>
</span> </span>
<div className="timeline-event ps-1"> <div className="timeline-event ps-1">
<div className="timeline-header"> <small className="text-success text-uppercase">
<small className="text-success text-uppercase"> Assigned By
Assigned By </small>
</small>
</div>
<h6 className="my-50">{job.assignedBy}</h6> <h6 className="my-50">{job.assignedBy}</h6>
<p className="text-body mb-0"> <p className="text-body mb-0">
{formatUTCToLocalTime(job.assignedAt)} {formatUTCToLocalTime(job.assignedAt)}
@ -122,23 +151,23 @@ const ServiceJobs = () => {
</div> </div>
</li> </li>
{/* Project */}
<li className="timeline-item ps-6 border-transparent"> <li className="timeline-item ps-6 border-transparent">
<span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none"> <span className="timeline-indicator-advanced timeline-indicator-primary border-0 shadow-none">
<i className="bx bx-map"></i> <i className="bx bx-map"></i>
</span> </span>
<div className="timeline-event ps-1"> <div className="timeline-event ps-1">
<div className="timeline-header"> <small className="text-primary text-uppercase">
<small className="text-primary text-uppercase">Project</small> Project
</div> </small>
<h6 className="my-50">{job.project}</h6> <h6 className="my-50">{job.project}</h6>
<p className="text-body mb-0">{job.title}</p> <p className="text-body mb-0">
{job.title}
</p>
</div> </div>
</li> </li>
</ul> </ul>
{/* Divider */} {index < list.length - 1 && (
{i < list.length - 1 && (
<div className="border-1 border-light border-top border-dashed my-4"></div> <div className="border-1 border-light border-top border-dashed my-4"></div>
)} )}
</React.Fragment> </React.Fragment>

View File

@ -59,12 +59,18 @@ const DocumentFilterPanel = forwardRef(
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => { resetFieldValue: (name, value) => {
if (value !== undefined) { if (value !== undefined) {
setValue(name, value); setValue(name, value, { shouldValidate: true, shouldDirty: true });
} else { } else {
reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] }); reset({ ...methods.getValues(), [name]: DocumentFilterDefaultValues[name] });
} }
if ((name === "startDate" || name === "endDate") && value === null) {
setTimeout(() => {
setResetKey((prev) => prev + 1);
}, 0);
}
}, },
getValues: methods.getValues, // optional, to read current filter state getValues: methods.getValues,
})); }));
//changes //changes
@ -141,6 +147,7 @@ const DocumentFilterPanel = forwardRef(
defaultRange={false} defaultRange={false}
resetSignal={resetKey} resetSignal={resetKey}
maxDate={new Date()} maxDate={new Date()}
className="w-100"
/> />
</div> </div>

View File

@ -124,26 +124,27 @@ const Documents = ({ Document_Entity, Entity }) => {
const updatedFilters = { ...filters }; const updatedFilters = { ...filters };
if (Array.isArray(updatedFilters[key])) { if (Array.isArray(updatedFilters[key])) {
updatedFilters[key] = updatedFilters[key].filter((v) => v !== id); updatedFilters[key] = updatedFilters[key].filter((v) => v !== id);
updatedRef.current?.resetFieldValue(key,updatedFilters[key]); updatedRef.current?.resetFieldValue(key, updatedFilters[key]);
} }
else if (key === "dateRange") { else if (key === "dateRange") {
updatedFilters.startDate = null; updatedFilters.startDate = null;
updatedFilters.endDate = null; updatedFilters.endDate = null;
updatedRef.current?.resetFieldValue("startDate",null); // These calls correctly tell the DocumentFilterPanel to update its form state:
updatedRef.current?.resetFieldValue("endDate",null); updatedRef.current?.resetFieldValue("startDate", null);
updatedRef.current?.resetFieldValue("endDate", null);
} }
else { else {
updatedFilters[key] = null; updatedFilters[key] = null;
} }
setFilter(updatedFilters); setFilter(updatedFilters);
return updatedFilters; return updatedFilters;
}; };
return ( return (
<DocumentContext.Provider value={contextValues}> <DocumentContext.Provider value={contextValues}>
<div className="mt-2"> <div className="mt-2">
<div className="card page-min-h d-flex p-5"> <div className="card page-min-h d-flex p-5">
<DocumentFilterChips filters={filters} filterData={filterData} removeFilterChip={removeFilterChip} />
<div className="row align-items-center"> <div className="row align-items-center">
{/* Search */} {/* Search */}
<div className="d-flex flex-row gap-2 col-12 col-md-8 col-lg-4 mb-md-0 align-items-center mb-2"> <div className="d-flex flex-row gap-2 col-12 col-md-8 col-lg-4 mb-md-0 align-items-center mb-2">
@ -194,6 +195,9 @@ const Documents = ({ Document_Entity, Entity }) => {
)} )}
</div> </div>
</div> </div>
<div className="ms-n1">
<DocumentFilterChips filters={filters} filterData={filterData} removeFilterChip={removeFilterChip} />
</div>
<DocumentsList <DocumentsList
Document_Entity={DocumentEntity} Document_Entity={DocumentEntity}
Entity={Entity} Entity={Entity}

View File

@ -127,7 +127,7 @@ const ManageEmployee = ({ employeeId, onClosed }) => {
const hasAccessAplication = watch("hasApplicationAccess"); const hasAccessAplication = watch("hasApplicationAccess");
return ( return (
<> <>
<form onSubmit={handleSubmit(onSubmit)} className="p-sm-0 p-2"> <form onSubmit={handleSubmit(onSubmit)} className="p-sm-2 p-2">
<div className="text-center"> <div className="text-center">
<p className="fs-5 fw-semibold"> <p className="fs-5 fw-semibold">
{" "} {" "}

View File

@ -94,6 +94,14 @@ const ExpenseFilterPanel = forwardRef(
reset({ ...methods.getValues(), [name]: defaultFilter[name] }); reset({ ...methods.getValues(), [name]: defaultFilter[name] });
} }
}, },
// --- START FIX: Add resetDateRange method ---
resetDateRange: () => {
setValue("startDate", null);
setValue("endDate", null);
// Trigger re-render/reset of the DateRangePicker component
setResetKey((prev) => prev + 1);
},
// --- END FIX ---
getValues: methods.getValues, // optional, to read current filter state getValues: methods.getValues, // optional, to read current filter state
})); }));
@ -137,13 +145,13 @@ const ExpenseFilterPanel = forwardRef(
: dynamicDefaultFilter.projectIds || [], : dynamicDefaultFilter.projectIds || [],
startDate: dynamicDefaultFilter.startDate startDate: dynamicDefaultFilter.startDate
? moment ? moment
.utc(dynamicDefaultFilter.startDate, "DD-MM-YYYY") .utc(dynamicDefaultFilter.startDate, "DD-MM-YYYY")
.toISOString() .toISOString()
: undefined, : undefined,
endDate: dynamicDefaultFilter.endDate endDate: dynamicDefaultFilter.endDate
? moment ? moment
.utc(dynamicDefaultFilter.endDate, "DD-MM-YYYY") .utc(dynamicDefaultFilter.endDate, "DD-MM-YYYY")
.toISOString() .toISOString()
: undefined, : undefined,
}; };
@ -176,18 +184,16 @@ const ExpenseFilterPanel = forwardRef(
<div className="d-inline-flex border rounded-pill mb-1 overflow-hidden shadow-none"> <div className="d-inline-flex border rounded-pill mb-1 overflow-hidden shadow-none">
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${ className={`btn px-2 py-1 rounded-0 text-tiny ${isTransactionDate ? "active btn-primary text-white" : ""
isTransactionDate ? "active btn-primary text-white" : "" }`}
}`}
onClick={() => setValue("isTransactionDate", true)} onClick={() => setValue("isTransactionDate", true)}
> >
Transaction Date Transaction Date
</button> </button>
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${ className={`btn px-2 py-1 rounded-0 text-tiny ${!isTransactionDate ? "active btn-primary text-white" : ""
!isTransactionDate ? "active btn-primary text-white" : "" }`}
}`}
onClick={() => setValue("isTransactionDate", false)} onClick={() => setValue("isTransactionDate", false)}
> >
Submitted Date Submitted Date

View File

@ -83,64 +83,64 @@ const ExpenseList = ({
} }
}; };
const groupByField = (items, field) => { const groupByField = (items, field) => {
if (!field || field === "none") { if (!field || field === "none") {
return { return {
All: { All: {
key: "All", key: "All",
displayField: "All", displayField: "All",
items: items || [] items: items || []
}
};
}
return items.reduce((acc, item) => {
let key;
let displayField;
switch (field) {
case "transactionDate":
key = formatUTCToLocalTime(item?.transactionDate);
displayField = "Transaction Date";
break;
case "status":
key = item?.status?.displayName || "Unknown";
displayField = "Status";
break;
case "submittedBy":
key = `${item?.createdBy?.firstName ?? ""} ${item?.createdBy?.lastName ?? ""}`.trim();
displayField = "Submitted By";
break;
case "project":
key = item?.project?.name || "Unknown Project";
displayField = "Project";
break;
case "paymentMode":
key = item?.paymentMode?.name || "Unknown Mode";
displayField = "Payment Mode";
break;
case "expenseCategory":
key = item?.expenseCategory?.name || "Unknown Type";
displayField = "Expense Category";
break;
case "createdAt":
key = item?.createdAt?.split("T")[0] || "Unknown Date";
displayField = "Created Date";
break;
default:
key = "Others";
displayField = "Others";
} }
};
}
return items.reduce((acc, item) => { const groupKey = `${field}_${key}`;
let key; if (!acc[groupKey]) {
let displayField; acc[groupKey] = { key, displayField, items: [] };
}
switch (field) { acc[groupKey].items.push(item);
case "transactionDate": return acc;
key = formatUTCToLocalTime(item?.transactionDate); }, {});
displayField = "Transaction Date"; };
break;
case "status":
key = item?.status?.displayName || "Unknown";
displayField = "Status";
break;
case "submittedBy":
key = `${item?.createdBy?.firstName ?? ""} ${item?.createdBy?.lastName ?? ""}`.trim();
displayField = "Submitted By";
break;
case "project":
key = item?.project?.name || "Unknown Project";
displayField = "Project";
break;
case "paymentMode":
key = item?.paymentMode?.name || "Unknown Mode";
displayField = "Payment Mode";
break;
case "expenseCategory":
key = item?.expenseCategory?.name || "Unknown Type";
displayField = "Expense Category";
break;
case "createdAt":
key = item?.createdAt?.split("T")[0] || "Unknown Date";
displayField = "Created Date";
break;
default:
key = "Others";
displayField = "Others";
}
const groupKey = `${field}_${key}`;
if (!acc[groupKey]) {
acc[groupKey] = { key, displayField, items: [] };
}
acc[groupKey].items.push(item);
return acc;
}, {});
};
const expenseColumns = [ const expenseColumns = [
@ -167,9 +167,8 @@ const groupByField = (items, field) => {
label: "Submitted By", label: "Submitted By",
align: "text-start", align: "text-start",
getValue: (e) => getValue: (e) =>
`${e.createdBy?.firstName ?? ""} ${ `${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
e.createdBy?.lastName ?? "" }`.trim() || "N/A",
}`.trim() || "N/A",
customRender: (e) => ( customRender: (e) => (
<div <div
className="d-flex align-items-center cursor-pointer" className="d-flex align-items-center cursor-pointer"
@ -182,9 +181,8 @@ const groupByField = (items, field) => {
lastName={e.createdBy?.lastName} lastName={e.createdBy?.lastName}
/> />
<span className="text-truncate"> <span className="text-truncate">
{`${e.createdBy?.firstName ?? ""} ${ {`${e.createdBy?.firstName ?? ""} ${e.createdBy?.lastName ?? ""
e.createdBy?.lastName ?? "" }`.trim() || "N/A"}
}`.trim() || "N/A"}
</span> </span>
</div> </div>
), ),
@ -216,9 +214,8 @@ const groupByField = (items, field) => {
align: "text-center", align: "text-center",
getValue: (e) => ( getValue: (e) => (
<span <span
className={`badge bg-label-${ className={`badge bg-label-${getColorNameFromHex(e?.status?.color) || "secondary"
getColorNameFromHex(e?.status?.color) || "secondary" }`}
}`}
> >
{e.status?.name || "Unknown"} {e.status?.name || "Unknown"}
</span> </span>
@ -238,16 +235,16 @@ const groupByField = (items, field) => {
return <ExpenseTableSkeleton headers={headers} />; return <ExpenseTableSkeleton headers={headers} />;
if (isError) return <div>{error?.message}</div>; if (isError) return <div>{error?.message}</div>;
const isNoGrouping = !groupBy || groupBy === "none"; const isNoGrouping = !groupBy || groupBy === "none";
const grouped = isNoGrouping const grouped = isNoGrouping
? { All: { key: "All", displayField: "All", items: data?.data ?? [] } } ? { All: { key: "All", displayField: "All", items: data?.data ?? [] } }
: groupByField(data?.data ?? [], groupBy); : groupByField(data?.data ?? [], groupBy);
const IsGroupedByDate = [ const IsGroupedByDate = [
{key:"none",displayField:"None"}, { key: "none", displayField: "None" },
{ key: "transactionDate", displayField: "Transaction Date" }, { key: "transactionDate", displayField: "Transaction Date" },
{ key: "createdAt", displayField: "created Date", }, { key: "createdAt", displayField: "created Date", },
]?.includes(groupBy); ]?.includes(groupBy);
@ -343,26 +340,22 @@ const grouped = isNoGrouping
(col.isAlwaysVisible || groupBy !== col.key) && ( (col.isAlwaysVisible || groupBy !== col.key) && (
<td <td
key={col.key} key={col.key}
className={`d-table-cell ml-2 ${ className={`d-table-cell ml-2 ${col.align ?? ""
col.align ?? "" } `}
} `}
> >
<div <div
className={`d-flex px-2 ${ className={`d-flex px-2 ${col.key === "status"
col.key === "status"
? "justify-content-center" ? "justify-content-center"
: "" : ""
} }
${ ${col.key === "amount"
col.key === "amount" ? "justify-content-end"
? "justify-content-end" : ""
: "" }
} ${col.key === "submitted"
${ ? "justify-content-center"
col.key === "submitted" : ""
? "justify-content-center" }
: ""
}
`} `}
> >
{col.customRender {col.customRender
@ -445,7 +438,18 @@ const grouped = isNoGrouping
<tr> <tr>
<td colSpan={8} className="text-center border-0 "> <td colSpan={8} className="text-center border-0 ">
<div className="py-8"> <div className="py-8">
<p>No Expense Found</p> <p
className="text-center"
style={{
height: "250px",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
No Expense found
</p>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -59,6 +59,28 @@ const PreviewDocument = ({ files = [] }) => {
}; };
}; };
const handleWheel = (e) => {
if (isDocumentType) return;
e.preventDefault();
const delta = e.deltaY;
setScale((prev) => {
let newScale = prev;
if (delta < 0) {
// Scroll UP -> Zoom IN
newScale = Math.min(prev + 0.1, MAX_ZOOM);
} else {
// Scroll DOWN -> Zoom OUT
newScale = Math.max(prev - 0.1, MIN_ZOOM);
}
return newScale;
});
};
const handleMouseMove = (e) => { const handleMouseMove = (e) => {
if (!dragging || isDocumentType) return; if (!dragging || isDocumentType) return;
@ -105,6 +127,8 @@ const PreviewDocument = ({ files = [] }) => {
</div> </div>
<div <div
onWheel={handleWheel}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}

View File

@ -13,37 +13,22 @@ const Sidebar = () => {
id="layout-menu" id="layout-menu"
className="layout-menu menu-vertical menu bg-menu-theme " className="layout-menu menu-vertical menu bg-menu-theme "
> >
<div className="app-brand" style={{ paddingLeft: "30px" }}> <div className="app-brand" style={{ paddingLeft: "15px" }}>
<Link to="/dashboard" className="app-brand-link"> <Link to="/dashboard" className="app-brand-link">
{/* <span className="app-brand-logo rounded-circle app-brand-logo-border"> <span className="app-brand-logo demo d-flex align-items-center">
<img <img
className="app-brand-logo-sidebar"
src="/img/brand/marco.png" src="/img/brand/marco.png"
alt="logo" width="50"
aria-label="logo image" height="40"
style={{ margin: "5px", paddingRight: "5px" }} alt="OnFieldWork logo"
/> />
</span> */} </span>
<a <span className="app-brand-text">
href="/" <span className="text-blue ms-1">OnField</span>
className="app-brand-link d-flex align-items-center gap-1 fw-bold navbar-brand " <span className="text-green">Work</span>
> <span className="text-dark">.com</span>
<span className="app-brand-logo demo d-flex align-items-center"> </span>
<img
src="/img/brand/marco.png"
width="40"
height="40"
alt="OnFieldWork logo"
/>
</span>
<span className="app-brand-text">
<span className="text-primary ">OnField</span>
<span className="mx-1">Work</span>
<span className="text-dark">.com</span>
</span>
</a>
</Link> </Link>
<small className="layout-menu-toggle menu-link text-large ms-auto cursor-pointer"> <small className="layout-menu-toggle menu-link text-large ms-auto cursor-pointer">

View File

@ -33,8 +33,11 @@ const AssignOrg = ({ setStep }) => {
}); });
const isPending = isPendingProject || isPendingTenat; const isPending = isPendingProject || isPendingTenat;
const mergedServices = useMemo(() => { const mergedServices = useMemo(() => {
if (!masterService || !projectServices) return []; const master = Array.isArray(masterService?.data) ? masterService.data : [];
const combined = [...masterService?.data, ...projectServices]; const project = Array.isArray(projectServices) ? projectServices : [];
const combined = [...master, ...project];
return combined.filter( return combined.filter(
(item, index, self) => index === self.findIndex((s) => s.id === item.id) (item, index, self) => index === self.findIndex((s) => s.id === item.id)
); );
@ -100,7 +103,8 @@ const AssignOrg = ({ setStep }) => {
alt="logo" alt="logo"
width={40} width={40}
height={40} height={40}
/> <p className="fw-semibold fs-5 mt-2 m-0">{orgData.name}</p> />{" "}
<p className="fw-semibold fs-5 mt-2 m-0">{orgData.name}</p>
</div> </div>
<div className="text-end"> <div className="text-end">
<button <button
@ -113,7 +117,10 @@ const AssignOrg = ({ setStep }) => {
</div> </div>
</div> </div>
</div> </div>
<div className="d-flex text-secondary mb-3"> <i className="bx bx-sm bx-info-circle me-2" /> Organization Info</div> <div className="d-flex text-secondary mb-3">
{" "}
<i className="bx bx-sm bx-info-circle me-2" /> Organization Info
</div>
{/* Contact Info */} {/* Contact Info */}
<div className="col-md-12 mb-4"> <div className="col-md-12 mb-4">
<div className="d-flex"> <div className="d-flex">
@ -132,7 +139,7 @@ const AssignOrg = ({ setStep }) => {
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
<i className='bx bx-sm me-1 bx-phone'></i> Contact Number : <i className="bx bx-sm me-1 bx-phone"></i> Contact Number :
</label> </label>
<div className="text-muted">{orgData.contactNumber}</div> <div className="text-muted">{orgData.contactNumber}</div>
</div> </div>
@ -143,7 +150,7 @@ const AssignOrg = ({ setStep }) => {
className="form-label me-2 mb-0 fw-semibold" className="form-label me-2 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
<i className='bx bx-sm me-1 bx-envelope'></i> Email Address : <i className="bx bx-sm me-1 bx-envelope"></i> Email Address :
</label> </label>
<div className="text-muted text-wrap">{orgData.email}</div> <div className="text-muted text-wrap">{orgData.email}</div>
</div> </div>
@ -166,7 +173,7 @@ const AssignOrg = ({ setStep }) => {
className="form-label me-1 mb-0 fw-semibold" className="form-label me-1 mb-0 fw-semibold"
style={{ minWidth: "130px" }} style={{ minWidth: "130px" }}
> >
<i className='bx bx-sm me-1 bx-map'></i> Address : <i className="bx bx-sm me-1 bx-map"></i> Address :
</label> </label>
<div className="text-muted text-start">{orgData.address}</div> <div className="text-muted text-start">{orgData.address}</div>
</div> </div>
@ -180,7 +187,11 @@ const AssignOrg = ({ setStep }) => {
<> <>
{/* Organization Type */} {/* Organization Type */}
<div className="mb-3 text-start"> <div className="mb-3 text-start">
<Label htmlFor="organizationTypeId" className="mb-3 fw-semibold" required> <Label
htmlFor="organizationTypeId"
className="mb-3 fw-semibold"
required
>
Organization Type Organization Type
</Label> </Label>
<div className="d-flex flex-wrap gap-3 mt-1"> <div className="d-flex flex-wrap gap-3 mt-1">
@ -214,7 +225,11 @@ const AssignOrg = ({ setStep }) => {
{/* Services */} {/* Services */}
<div className="mb-3"> <div className="mb-3">
<Label htmlFor="serviceIds" className="mb-3 fw-semibold" required> <Label
htmlFor="serviceIds"
className="mb-3 fw-semibold"
required
>
Select Services Select Services
</Label> </Label>
{mergedServices?.map((service) => ( {mergedServices?.map((service) => (
@ -255,8 +270,8 @@ const AssignOrg = ({ setStep }) => {
{isPending {isPending
? "Please wait..." ? "Please wait..."
: flowType === "default" : flowType === "default"
? "Assign to Organization" ? "Assign to Organization"
: "Assign to Project"} : "Assign to Project"}
</button> </button>
</div> </div>
</form> </form>

View File

@ -1,3 +1,4 @@
import moment from "moment";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
const PaymentRequestFilterChips = ({ filters, filterData, removeFilterChip, clearFilter }) => { const PaymentRequestFilterChips = ({ filters, filterData, removeFilterChip, clearFilter }) => {
@ -22,6 +23,21 @@ const PaymentRequestFilterChips = ({ filters, filterData, removeFilterChip, clea
addGroup(filters.projectIds, data.projects, "Projects", "projectIds"); addGroup(filters.projectIds, data.projects, "Projects", "projectIds");
addGroup(filters.statusIds, data.status, "Status", "statusIds"); addGroup(filters.statusIds, data.status, "Status", "statusIds");
if (filters.startDate || filters.endDate) {
const start = filters.startDate ? moment(filters.startDate).format("DD MMM YYYY") : "";
const end = filters.endDate ? moment(filters.endDate).format("DD MMM YYYY") : "";
chips.push({
key: "dateRange",
label: "Date",
items: [
{
id: "dateRange",
name: start && end ? `${start} to ${end}` : start || end,
},
],
});
}
return chips; return chips;
}, [filters, filterData]); }, [filters, filterData]);

View File

@ -76,9 +76,18 @@ const PaymentRequestFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilte
if (value !== undefined) { if (value !== undefined) {
setValue(name, value); setValue(name, value);
} else { } else {
reset({ ...methods.getValues(), [name]: defaultFilter[name] }); // NOTE: Using defaultPaymentRequestFilter for clear functionality
reset({ ...methods.getValues(), [name]: defaultPaymentRequestFilter[name] });
} }
}, },
// --- START FIX: Add resetDateRange method ---
resetDateRange: () => {
setValue("startDate", null);
setValue("endDate", null);
// Trigger re-render/reset of the DateRangePicker component
setResetKey((prev) => prev + 1);
},
// --- END FIX ---
getValues: methods.getValues, // optional, to read current filter state getValues: methods.getValues, // optional, to read current filter state
})); }));
@ -94,7 +103,7 @@ const PaymentRequestFilterPanel = forwardRef(({ onApply, handleGroupBy, setFilte
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(), startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(), endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(),
}); });
handleGroupBy(selectedGroup.id); // handleGroupBy(selectedGroup.id);
// closePanel(); // closePanel();
}; };

View File

@ -355,8 +355,18 @@ const PaymentRequestList = ({ filters, filterData, removeFilterChip, clearFilter
) : ( ) : (
<tr> <tr>
<td colSpan={8} className="text-center border-0 "> <td colSpan={8} className="text-center border-0 ">
<div className="py-8"> <div >
<p>No Request Found</p> <p
className="text-center"
style={{
height: "250px",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
>
No data found
</p>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -237,13 +237,116 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
))} ))}
</span> </span>
</p> </p>
<div
className="col-12 col-md-4 d-flex flex-row gap-3 align-items-center mt-4 mb-6 justify-content-end"
ref={dropdownRef}
>
<div className="d-flex justify-content-between align-items-center w-100">
{/* Left Side → Heading + Search */}
<div className="d-flex flex-column w-100">
<label className=" text-start small mb-1">Search Employee</label>
<input
type="text"
className="form-control form-control-sm"
placeholder="Search employees or roles..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
{/* Right Side → Dropdown */}
<div className="dropdown position-relative d-inline-block ms-2 mt-4">
<a
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") || selectedRoles.length === 0
? "text-secondary"
: "text-primary"
}`}
onClick={() => setOpen(!open)}
>
<i className="bx bx-slider-alt ms-2"></i>
</a>
{/* Badge */}
{selectedRolesCount > 0 && (
<span
className="position-absolute top-0 start-100 translate-middle badge rounded-circle bg-warning text-white text-tiny"
style={{
fontSize: "0.65rem",
minWidth: "18px",
height: "18px",
padding: "0",
lineHeight: "18px",
textAlign: "center",
zIndex: 10,
}}
>
{selectedRolesCount}
</span>
)}
{/* Dropdown Menu */}
{open && (
<ul
className="dropdown-menu show p-2 text-capitalize"
style={{ maxHeight: "300px", overflowY: "auto" }}
>
{/* All Roles */}
<li key="all">
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id="checkboxAllRoles"
value="all"
checked={selectedRoles.includes("all")}
onChange={(e) => handleRoleChange(e, e.target.value)}
/>
<label
className="form-check-label ms-2"
htmlFor="checkboxAllRoles"
>
All Roles
</label>
</div>
</li>
{/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id={`checkboxRole-${role.id}`}
value={role.id}
checked={selectedRoles.includes(String(role.id))}
onChange={(e) => handleRoleChange(e, e.target.value)}
/>
<label
className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`}
>
{role.name}
</label>
</div>
</li>
))}
</ul>
)}
</div>
</div>
</div>
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="form-label text-start"> <div className="form-label text-start">
<div className="row mb-1"> <div className="row mb-1">
<div className="col-12"> <div className="col-12">
<div className="row text-start"> <div className="row text-start">
<div className="col-12 col-md-8 d-flex flex-row gap-3 align-items-center mt-4"> <div className="col-12 col-md-8 d-flex flex-row gap-3 align-items-center mt-4 d-none">
<div> <div>
<AppFormController <AppFormController
name="organizationId" name="organizationId"
@ -296,109 +399,7 @@ const AssignTask = ({ assignData, onClose, setAssigned }) => {
)} )}
</div> </div>
</div> </div>
<div
className="col-12 col-md-4 d-flex flex-row gap-3 align-items-center justify-content-end"
ref={dropdownRef}
>
{/* Dropdown */}
<div className="dropdown position-relative d-inline-block">
<a
className={`dropdown-toggle hide-arrow cursor-pointer ${selectedRoles.includes("all") ||
selectedRoles.length === 0
? "text-secondary"
: "text-primary"
}`}
onClick={() => setOpen(!open)}
>
<i className="bx bx-slider-alt ms-2"></i>
</a>
{/* Badge */}
{selectedRolesCount > 0 && (
<span
className="position-absolute top-0 start-100 translate-middle badge rounded-circle bg-warning text-white text-tiny"
style={{
fontSize: "0.65rem",
minWidth: "18px",
height: "18px",
padding: "0",
lineHeight: "18px",
textAlign: "center",
zIndex: 10,
}}
>
{selectedRolesCount}
</span>
)}
{/* Dropdown Menu */}
{open && (
<ul
className="dropdown-menu show p-2 text-capitalize"
style={{ maxHeight: "300px", overflowY: "auto" }}
>
{/* All Roles */}
<li key="all">
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id="checkboxAllRoles"
value="all"
checked={selectedRoles.includes("all")}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor="checkboxAllRoles"
>
All Roles
</label>
</div>
</li>
{/* Dynamic Roles */}
{jobRolesForDropdown?.map((role) => (
<li key={role.id}>
<div className="form-check dropdown-item py-0">
<input
className="form-check-input"
type="checkbox"
id={`checkboxRole-${role.id}`}
value={role.id}
checked={selectedRoles.includes(
String(role.id)
)}
onChange={(e) =>
handleRoleChange(e, e.target.value)
}
/>
<label
className="form-check-label ms-2"
htmlFor={`checkboxRole-${role.id}`}
>
{role.name}
</label>
</div>
</li>
))}
</ul>
)}
</div>
{/* Search Box */}
<div>
<input
type="text"
className="form-control form-control-sm ms-auto mb-2 mt-2"
placeholder="Search employees or roles..."
value={searchTerm}
onChange={handleSearchChange}
/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,7 +8,7 @@ import {
useGroups, useGroups,
useWorkCategoriesMaster, useWorkCategoriesMaster,
} from "../../../hooks/masterHook/useMaster"; } from "../../../hooks/masterHook/useMaster";
import { useManageTask, useProjectAssignedOrganizationsName, useProjectAssignedServices } from "../../../hooks/useProjects"; import { useCurrentService, useManageTask, useProjectAssignedOrganizationsName, useProjectAssignedServices } from "../../../hooks/useProjects";
import showToast from "../../../services/toastService"; import showToast from "../../../services/toastService";
import Label from "../../common/Label"; import Label from "../../common/Label";
import { useSelectedProject } from "../../../slices/apiDataManager"; import { useSelectedProject } from "../../../slices/apiDataManager";
@ -28,12 +28,16 @@ const taskSchema = z.object({
comment: z.string(), comment: z.string(),
}); });
const defaultModel = {
const TaskModel = ({ project, onSubmit, onClose }) => {
const currentService = useCurrentService()
const defaultModel = {
id: null, id: null,
buildingID: "", buildingID: "",
floorId: "", floorId: "",
workAreaId: "", workAreaId: "",
serviceId: "", serviceId: currentService ?? "",
activityGroupId: "", activityGroupId: "",
activityID: "", activityID: "",
workCategoryId: "", workCategoryId: "",
@ -41,8 +45,6 @@ const defaultModel = {
completedWork: 0, completedWork: 0,
comment: "", comment: "",
}; };
const TaskModel = ({ project, onSubmit, onClose }) => {
// const { activities, loading: activityLoading } = useActivitiesMaster(); // const { activities, loading: activityLoading } = useActivitiesMaster();
const { categories, categoryLoading } = useWorkCategoriesMaster(); const { categories, categoryLoading } = useWorkCategoriesMaster();

View File

@ -105,7 +105,7 @@ const ProjectCard = ({ project, isCore = true }) => {
> >
{project?.shortName ? project?.shortName : project?.name} {project?.shortName ? project?.shortName : project?.name}
</h5> </h5>
<div className="client-info text-body"> <div className="client-info text-body text-start">
<span>{project?.shortName ? project?.name : ""}</span> <span>{project?.shortName ? project?.name : ""}</span>
</div> </div>
</div> </div>

View File

@ -55,7 +55,7 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
const { data: assignedServices, isLoading: servicesLoading } = const { data: assignedServices, isLoading: servicesLoading } =
useProjectAssignedServices(projectId); useProjectAssignedServices(projectId);
const { control } = useForm({ const { control, setValue } = useForm({
defaultValues: { defaultValues: {
serviceId: selectedService || "", serviceId: selectedService || "",
}, },
@ -74,6 +74,22 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
}; };
useEffect(() => {
if (!servicesLoading && assignedServices?.length === 1) {
const serviceId = assignedServices[0].id;
// set form value
setValue("serviceId", serviceId, {
shouldDirty: true,
shouldValidate: true,
});
// sync redux
dispatch(setService(serviceId));
}
}, [assignedServices, servicesLoading, setValue, dispatch]);
return ( return (
<> <>
{showModalBuilding && ( {showModalBuilding && (
@ -132,31 +148,27 @@ const ProjectInfra = ({ data, onDataChange, eachSiteEngineer }) => {
<div className="col-md-4 col-12 dataTables_length text-start py-2 px-2"> <div className="col-md-4 col-12 dataTables_length text-start py-2 px-2">
<div className="ms-4 mt-n1"> <div className="ms-4 mt-n1">
{!servicesLoading && assignedServices?.length > 0 && ( {!servicesLoading && assignedServices?.length > 0 && (
assignedServices.length > 1 ? ( <AppFormController
<AppFormController name="serviceId"
name="serviceId" control={control}
control={control} render={({ field }) => (
render={({ field }) => ( <SelectField
<SelectField label="Select Service"
label="Select Service" options={[...(assignedServices ?? [])]}
options={[{ id: "", name: "All Projects" }, ...(assignedServices ?? [])]} placeholder="Choose a Service"
placeholder="Choose a Service" labelKey="name"
required valueKey="id"
labelKey="name" value={field.value}
valueKey="id" onChange={(val) => {
value={field.value} field.onChange(val);
onChange={(val) => { dispatch(setService(val));
field.onChange(val); }}
handleServiceChange(val); isLoading={servicesLoading}
}} />
isLoading={servicesLoading} )}
/> />
)}
/>
) : (
<h5>{assignedServices[0].name}</h5>
)
)} )}
</div> </div>
</div> </div>

View File

@ -159,8 +159,9 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
</td> </td>
))} ))}
<td <td
className={`mx-2 ${canManageProject ? "d-sm-table-cell" : "d-none" className={`mx-2 ${
}`} canManageProject ? "d-sm-table-cell" : "d-none"
}`}
> >
<div className="dropdown z-2"> <div className="dropdown z-2">
<button <button
@ -238,7 +239,7 @@ const ProjectListView = ({ data, currentPage, totalPages, paginate }) => {
<Pagination <Pagination
currentPage={currentPage} currentPage={currentPage}
totalPages={totalPages} totalPages={totalPages}
paginate={paginate} onPageChange={paginate}
/> />
</div> </div>
); );

View File

@ -170,7 +170,7 @@ const ProjectStatistics = ({ project }) => {
<h5 className="card-action-title mb-0"> <h5 className="card-action-title mb-0">
{" "} {" "}
<i className="fa fa-line-chart rounded-circle text-primary"></i> <i className="fa fa-line-chart rounded-circle text-primary"></i>
<span className="ms-2 fw-bold">Project Statistics</span> <span className="ms-2 fw-semibold">Project Statistics</span>
</h5> </h5>
</div> </div>
<div className="card-body"> <div className="card-body">

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import TeamEmployeeList from "./TeamEmployeeList"; import TeamEmployeeList from "./TeamEmployeeList";
import { useOrganization } from "../../../hooks/useDirectory"; import { useOrganization } from "../../../hooks/useDirectory";
import { useOrganizationsList } from "../../../hooks/useOrganization"; import { useOrganizationsList } from "../../../hooks/useOrganization";
@ -14,40 +14,50 @@ const TeamAssignToProject = ({ closeModal }) => {
const project = useSelectedProject(); const project = useSelectedProject();
const { data, isLoading, isError, error } = const { data, isLoading, isError, error } =
useProjectAssignedOrganizationsName(project); useProjectAssignedOrganizationsName(project);
const { control, watch, formState: { errors } } = useForm({ const { control, watch, setValue, formState: { errors } } = useForm({
defaultValues: { organizationId: "" }, defaultValues: { organizationId: "" },
}); });
useEffect(() => {
if (data?.length === 1) {
setValue("organizationId", data[0].id, {
shouldValidate: true,
shouldDirty: true,
});
}
}, [data, setValue]);
return ( return (
<div className="container"> <div className="container">
{/* <p className="fs-5 fs-seminbod ">Assign Employee To Project </p> */} {/* <p className="fs-5 fs-seminbod ">Assign Employee To Project </p> */}
<h5 className="mb-4">Assign Employee To Project</h5> <h5 className="mb-4">Assign Employee To Project</h5>
<div className="row align-items-center gx-5 text-start"> <div className="row align-items-center gx-5 text-start">
<div className="col-12 col-md-6 mb-2"> <div className="col-12 col-md-6 mb-2">
<AppFormController <AppFormController
name="organizationId" name="organizationId"
control={control} control={control}
rules={{ required: "Organization is required" }} rules={{ required: "Organization is required" }}
render={({ field }) => ( render={({ field }) => (
<SelectField <SelectField
label="Select Organization" label="Select Organization"
options={data ?? []} options={data ?? []}
placeholder="Choose an Organization" placeholder="Choose an Organization"
required required
labelKey="name" labelKey="name"
valueKey="id" valueKey="id"
value={field.value} value={field.value}
onChange={field.onChange} onChange={field.onChange}
isLoading={isLoading} isLoading={isLoading}
className="m-0 w-100" className="m-0 w-100"
/> />
)}
/>
{errors.organizationId && (
<small className="danger-text">{errors.organizationId.message}</small>
)} )}
</div> />
<div className="col-12 col-md-6 mt-n5"> {errors.organizationId && (
<small className="danger-text">{errors.organizationId.message}</small>
)}
</div>
<div className="col-12 col-md-6 mt-n2">
<div className="d-flex flex-column"> <div className="d-flex flex-column">
<label htmlFor="search" className="form-label mb-1"> <label htmlFor="search" className="form-label mb-1">
Search Employee Search Employee

View File

@ -162,15 +162,23 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
<table className="table" style={{ maxHeight: "80px", overflowY: "auto" }}> <table className="table" style={{ maxHeight: "80px", overflowY: "auto" }}>
<thead className=" position-sticky top-0"> <thead className=" position-sticky top-0">
<tr> <tr>
<th>Select</th>
<th>Employee</th> <th>Employee</th>
<th>Service</th> <th>Service</th>
<th>Job Role</th> <th>Job Role</th>
<th>Select</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{employees.map((emp, index) => ( {employees.map((emp, index) => (
<tr key={emp.id}> <tr key={emp.id}>
<td>
<input
type="checkbox"
className="form-check-input"
checked={emp.isChecked}
onChange={() => handleCheckboxChange(index)}
/>
</td>
<td> <td>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<Avatar firstName={emp.firstName} lastName={emp.lastName} /> <Avatar firstName={emp.firstName} lastName={emp.lastName} />
@ -179,6 +187,7 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
</span> </span>
</div> </div>
</td> </td>
<td> <td>
<select <select
value={emp.serviceId} value={emp.serviceId}
@ -221,14 +230,7 @@ const TeamEmployeeList = ({ organizationId, searchTerm, closeModal }) => {
<div className="danger-text">{emp.errors.jobRole}</div> <div className="danger-text">{emp.errors.jobRole}</div>
)} )}
</td> </td>
<td>
<input
type="checkbox"
className="form-check-input"
checked={emp.isChecked}
onChange={() => handleCheckboxChange(index)}
/>
</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

View File

@ -34,7 +34,7 @@ const Teams = () => {
const [selectedEmployee, setSelectedEmployee] = useState(null); const [selectedEmployee, setSelectedEmployee] = useState(null);
const [deleteEmployee, setDeleteEmplyee] = useState(null); const [deleteEmployee, setDeleteEmplyee] = useState(null);
const [activeEmployee, setActiveEmployee] = useState(false); const [activeEmployee, setActiveEmployee] = useState(false);
const { control, watch } = useForm({ const { control, watch, setValue } = useForm({
defaultValues: { defaultValues: {
selectedService: "", selectedService: "",
searchTerm: "", searchTerm: "",
@ -136,6 +136,18 @@ const Teams = () => {
return () => eventBus.off("employee", employeeHandler); return () => eventBus.off("employee", employeeHandler);
}, [employeeHandler]); }, [employeeHandler]);
useEffect(() => {
if (!servicesLoading && assignedServices?.length === 1) {
const serviceId = assignedServices[0].id;
setValue("selectedService", serviceId, {
shouldDirty: true,
shouldValidate: true,
});
}
}, [assignedServices, servicesLoading, setValue]);
return ( return (
<> <>
{AssigTeam && ( {AssigTeam && (
@ -162,37 +174,30 @@ const Teams = () => {
<div className="card-body"> <div className="card-body">
<div className="row align-items-center justify-content-between mb-4 g-3"> <div className="row align-items-center justify-content-between mb-4 g-3">
<div className="col-md-6 col-12 d-flex flex-wrap align-items-center gap-3"> <div className="col-md-6 col-12 d-flex flex-wrap align-items-center gap-3">
{!servicesLoading && assignedServices && ( {!servicesLoading && assignedServices?.length > 0 && (
<> <div className="col-12 col-md-6 mb-2 text-start">
{assignedServices.length === 1 && ( <AppFormController
<h5 className="mb-2">{assignedServices[0].name}</h5> name="selectedService"
)} control={control}
render={({ field }) => (
{assignedServices.length > 1 && ( <SelectField
<div className="col-12 col-md-6 mb-2 text-start"> label="Select Service"
<AppFormController options={[ ...assignedServices]}
name="selectedService" placeholder="Choose a Service"
control={control} labelKey="name"
render={({ field }) => ( valueKey="id"
<SelectField value={field.value}
label="Select Service" onChange={field.onChange}
options={[{ id: "", name: "All Services" }, ...assignedServices]} isLoading={servicesLoading}
placeholder="Choose a Service" className="w-100"
labelKey="name"
valueKey="id"
value={field.value}
onChange={field.onChange}
isLoading={servicesLoading}
className="w-100"
/>
)}
/> />
</div> )}
)} />
</> </div>
)} )}
<div className="form-check form-switch d-flex align-items-center text-nowrap"> <div className="form-check form-switch d-flex align-items-center text-nowrap">
<input <input
type="checkbox" type="checkbox"

View File

@ -56,7 +56,7 @@ const OrganizationInfo = ({ onNext, onPrev, onSubmitTenant }) => {
// onSubmitTenant(data); // onSubmitTenant(data);
// onNext(); // onNext();
const tenantPayload = { ...data, onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() } const tenantPayload = { ...data, onBoardingDate: moment.utc(data.onBoardingDate, "DD-MM-YYYY").toISOString() }
// CreateTenant(tenantPayload); CreateTenant(tenantPayload);
} }
}; };

View File

@ -0,0 +1,100 @@
import React, { useMemo } from "react";
import { TENANT_STATUS, reference } from "../../utils/constants";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
const TenantFilterChips = ({ filter, filterData, removeFilterChip, clearFilter }) => {
const data = filterData || {};
const filterChips = useMemo(() => {
const chips = [];
const addGroup = (ids, list, label, key, valueKey = "id") => {
if (!ids || ids.length === 0) return;
const items = ids.map((id) => {
const found = list?.find((i) => i[valueKey] === id);
return {
id,
name: found?.name || id,
};
});
chips.push({ key, label, items });
};
// Industries
addGroup(filter?.industryIds, data, "Industries", "industryIds");
// References
addGroup(filter?.references, reference, "References", "references", "val");
// Tenant Status
addGroup(filter?.tenantStatusIds, TENANT_STATUS, "Status", "tenantStatusIds");
// Date Range Chip
if (filter?.startDate || filter?.endDate) {
chips.push({
key: "date",
label: "Date Range",
items: [
{
id: "date-range",
name: `${filter?.startDate ? formatUTCToLocalTime(filter.startDate) : ""}${
filter?.endDate ? " to " + formatUTCToLocalTime(filter.endDate) : ""
}`,
},
],
});
}
return chips;
}, [filter, filterData]);
if (!filterChips.length) return null;
return (
<div className="d-flex flex-wrap align-items-center gap-2 mt-2">
{filterChips.map((chipGroup) => (
<div key={chipGroup.key} className="d-flex align-items-center flex-wrap">
<span className="fw-semibold me-2">{chipGroup.label}:</span>
{chipGroup.items.map((item) => (
<span
key={item.id}
className="d-flex align-items-center bg-light rounded px-2 py-1 me-1"
>
<span>{item.name}</span>
{/* Remove Date Range → remove full filter */}
{chipGroup.key === "date" ? (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={clearFilter}
/>
) : (
<button
type="button"
className="btn-close btn-close-white btn-sm ms-2"
style={{
filter: "invert(1) grayscale(1)",
opacity: 0.7,
fontSize: "0.6rem",
}}
onClick={() => removeFilterChip(chipGroup.key, item.id)}
/>
)}
</span>
))}
</div>
))}
</div>
);
};
export default TenantFilterChips;

View File

@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import React, { useState, useCallback, useEffect } from "react"; import React, { useState, useCallback, useEffect, useImperativeHandle, forwardRef, useMemo } from "react";
import { FormProvider, useForm, useFormContext } from "react-hook-form"; import { FormProvider, useForm, useFormContext } from "react-hook-form";
import { defaultFilterValues, filterSchema } from "./TenantSchema"; import { defaultFilterValues, filterSchema } from "./TenantSchema";
import Label from "../common/Label"; import Label from "../common/Label";
@ -10,16 +10,44 @@ import { DateRangePicker1 } from "../common/DateRangePicker";
import moment from "moment"; import moment from "moment";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
const TenantFilterPanel = ({ onApply }) => { const TenantFilterPanel = forwardRef(({ onApply, setFilterdata, clearFilter }, ref) => {
const [resetKey, setResetKey] = useState(0); const [resetKey, setResetKey] = useState(0);
const methods = useForm({ const methods = useForm({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
defaultValues: defaultFilterValues, defaultValues: defaultFilterValues,
}); });
const { handleSubmit, reset, setValue } = methods;
const { data: industries, isLoading } = useIndustries();
const dynamicDefaultFilter = useMemo(() => {
return {
...defaultFilterValues,
industryIds: defaultFilterValues.industryIds || [],
tenantStatusIds: defaultFilterValues.tenantStatusIds || [],
references: defaultFilterValues.references || [],
startDate: defaultFilterValues.startDate,
endDate: defaultFilterValues.endDate,
};
}, [defaultFilterValues]);
useImperativeHandle(ref, () => ({
resetFieldValue: (name, value) => {
setValue(name, value);
},
resetFields: () => {
reset(defaultFilterValues);
setResetKey(prev => prev + 1); // reset date picker
}
}));
useEffect(() => {
if (industries && setFilterdata) {
setFilterdata(industries);
}
}, [industries, setFilterdata]);
const { handleSubmit, reset } = methods;
const { data: industries = [], isLoading } = useIndustries();
const handleClosePanel = useCallback(() => { const handleClosePanel = useCallback(() => {
document.querySelector(".offcanvas.show .btn-close")?.click(); document.querySelector(".offcanvas.show .btn-close")?.click();
@ -32,9 +60,8 @@ const TenantFilterPanel = ({ onApply }) => {
startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(), startDate: moment.utc(formData.startDate, "DD-MM-YYYY").toISOString(),
endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(), endDate: moment.utc(formData.endDate, "DD-MM-YYYY").toISOString(),
}); });
handleClosePanel();
}, },
[onApply, handleClosePanel] [onApply]
); );
@ -65,6 +92,7 @@ const TenantFilterPanel = ({ onApply }) => {
endField="endDate" endField="endDate"
resetSignal={resetKey} resetSignal={resetKey}
defaultRange={false} defaultRange={false}
className="w-100"
/> />
</div> </div>
<div className="text-strat mb-2"> <div className="text-strat mb-2">
@ -119,6 +147,6 @@ const TenantFilterPanel = ({ onApply }) => {
</form> </form>
</FormProvider> </FormProvider>
); );
}; });
export default TenantFilterPanel; export default TenantFilterPanel;

View File

@ -7,12 +7,14 @@ import Pagination from "../common/Pagination";
import { TenantTableSkeleton } from "./TenanatSkeleton"; import { TenantTableSkeleton } from "./TenanatSkeleton";
import { useTenantContext } from "../../pages/Tenant/TenantPage"; import { useTenantContext } from "../../pages/Tenant/TenantPage";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import TenantFilterChips from "./TenantFilterChips";
const TenantsList = ({ const TenantsList = ({
filters, filters,
searchText, searchText,
setIsRefetching, setIsRefetching,
setRefetchFn, setRefetchFn,
filterData, removeFilterChip, clearFilter
}) => { }) => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const navigate = useNavigate(); const navigate = useNavigate();
@ -135,54 +137,65 @@ const TenantsList = ({
); );
return ( return (
<> <>
<div className=" mt-3"> <div className="main-content">
<div className=" text-nowrap table-responsive"> <div className="col-12 mb-2 mt-2 px-4">
<table className="table border-top dataTable text-nowrap"> <TenantFilterChips
<thead> filter={filters}
<tr className="shadow-sm"> filterData={filterData}
{TenantColumns.map((col) => ( removeFilterChip={removeFilterChip}
<th key={col.key} className="sorting d-table-cell"> clearFilter={clearFilter}
<div className={col.align}>{col.label}</div> />
</th>
))} </div>
</tr> <div className=" mt-3">
</thead> <div className=" text-nowrap table-responsive">
<tbody> <table className="table border-top dataTable text-nowrap">
{data?.data.length > 0 ? ( <thead>
data.data.map((tenant) => ( <tr className="shadow-sm">
<tr key={tenant.id} style={{ height: "50px" }}> {TenantColumns.map((col) => (
{TenantColumns.map((col) => ( <th key={col.key} className="sorting d-table-cell">
<td <div className={col.align}>{col.label}</div>
key={col.key} </th>
className={`d-table-cell px-3 py-2 align-middle ${col.align ?? "" ))}
}`}
>
{col.customRender
? col.customRender(tenant)
: col.getValue(tenant)}
</td>
))}
</tr>
))
) : (
<tr>
<td
colSpan={TenantColumns.length + 1}
className="text-center py-4 border-0"
>
No Tenants Found
</td>
</tr> </tr>
)} </thead>
</tbody> <tbody>
</table> {data?.data.length > 0 ? (
{data?.data?.length > 0 && ( data.data.map((tenant) => (
<Pagination <tr key={tenant.id} style={{ height: "50px" }}>
currentPage={currentPage} {TenantColumns.map((col) => (
totalPages={data.totalPages} <td
onPageChange={paginate} key={col.key}
/> className={`d-table-cell px-3 py-2 align-middle ${col.align ?? ""
)} }`}
>
{col.customRender
? col.customRender(tenant)
: col.getValue(tenant)}
</td>
))}
</tr>
))
) : (
<tr>
<td
colSpan={TenantColumns.length + 1}
className="text-center py-4 border-0"
>
No Tenants Found
</td>
</tr>
)}
</tbody>
</table>
{data?.data?.length > 0 && (
<Pagination
currentPage={currentPage}
totalPages={data.totalPages}
onPageChange={paginate}
/>
)}
</div>
</div> </div>
</div> </div>
</> </>

View File

@ -3,6 +3,7 @@ import { useController } from "react-hook-form";
const DatePicker = ({ const DatePicker = ({
name, name,
defaultDate,
control, control,
size="sm", size="sm",
placeholder = "DD-MM-YYYY", placeholder = "DD-MM-YYYY",

View File

@ -93,6 +93,7 @@ export const DateRangePicker1 = ({
resetSignal, resetSignal,
defaultRange = true, defaultRange = true,
maxDate = null, maxDate = null,
pastDays = 6,
...rest ...rest
}) => { }) => {
const inputRef = useRef(null); const inputRef = useRef(null);
@ -105,7 +106,7 @@ export const DateRangePicker1 = ({
const applyDefaultDates = () => { const applyDefaultDates = () => {
const today = new Date(); const today = new Date();
const past = new Date(); const past = new Date();
past.setDate(today.getDate() - 6); past.setDate(today.getDate() - pastDays);
const format = (d) => flatpickr.formatDate(d, "d-m-Y"); const format = (d) => flatpickr.formatDate(d, "d-m-Y");
const formattedStart = format(past); const formattedStart = format(past);

View File

@ -2,10 +2,17 @@ import React from "react";
import { useDeliverChallane } from "../../hooks/usePurchase"; import { useDeliverChallane } from "../../hooks/usePurchase";
import { SpinnerLoader } from "../common/Loader"; import { SpinnerLoader } from "../common/Loader";
import { formatUTCToLocalTime } from "../../utils/dateUtils"; import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { FileView } from "../Expenses/Filelist"; import { FileView } from "../Expenses/Filelist"; // Assuming FileView is the component showing the file icon/name
import { usePurchaseContext } from "../../pages/purchase/PurchasePage";
import { getIconByFileType } from "../../utils/appUtils";
// Assuming you have an Error component imported somewhere else
// import Error from "../common/Error";
const DeliverChallanList = ({ purchaseId, viewDocuments }) => { const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
const { setDocumentView } = usePurchaseContext();
const { data, isLoading, isError, error } = useDeliverChallane(purchaseId); const { data, isLoading, isError, error } = useDeliverChallane(purchaseId);
if (isLoading) { if (isLoading) {
return ( return (
<div className="d-flex justify-content-center align-items-center text-center vh-50"> <div className="d-flex justify-content-center align-items-center text-center vh-50">
@ -17,10 +24,12 @@ const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
if (isError) { if (isError) {
return ( return (
<div className="py-3"> <div className="py-3">
{/* Assuming Error component is used here */}
<Error error={error} /> <Error error={error} />
</div> </div>
); );
} }
if (!isLoading && data.length === 0) if (!isLoading && data.length === 0)
return ( return (
<div className="d-flex justify-content-center align-items-center text-center vh-50"> <div className="d-flex justify-content-center align-items-center text-center vh-50">
@ -55,8 +64,29 @@ const DeliverChallanList = ({ purchaseId, viewDocuments }) => {
{item.description || "-"} {item.description || "-"}
</p> </p>
{/* Check if attachment exists and open document view on click */}
{item.attachment?.preSignedUrl && ( {item.attachment?.preSignedUrl && (
<FileView file={item.attachment} viewFile={viewDocuments} /> <div
className="d-flex align-items-center cusor-pointer mt-2"
onClick={() => {
setDocumentView({
IsOpen: true,
Images: [item.attachment],
});
}}
>
{/* Replicating the display style used in ViewExpense for single file attachment */}
<i
className={`bx ${getIconByFileType(item.attachment.contentType)}`}
style={{ fontSize: "30px" }}
></i>
<small
className="text-start text-tiny text-truncate w-100 ms-1"
title={item.attachment.fileName}
>
{item.attachment.fileName}
</small>
</div>
)} )}
</div> </div>
</div> </div>

View File

@ -0,0 +1,31 @@
const ActivitiesTable = ({ date, rows }) => {
return (
<div className="reports-activities">
<h2>Activities (Tasks) Performed {date}</h2>
<table className="reports-table">
<thead>
<tr>
<th>NAME</th>
<th>JOB ROLE</th>
<th>CHECK IN</th>
<th>CHECK OUT</th>
</tr>
</thead>
<tbody>
{rows.map((row, i) => (
<tr key={i}>
<td>{row.name}</td>
<td>{row.role}</td>
<td>{row.in}</td>
<td>{row.out}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default ActivitiesTable;

View File

@ -0,0 +1,121 @@
import React from "react";
import Chart from "react-apexcharts";
import { formatFigure } from "../../utils/appUtils";
const Progress = ({
color = "#00aaff",
series = 0,
total = 0,
height = 100,
width = 100,
completed = 0,
}) => {
const options = {
chart: {
type: "radialBar",
},
colors: [color],
plotOptions: {
radialBar: {
hollow: { size: "55%" },
track: { background: "#f1f1f1" },
dataLabels: {
name: { show: false },
value: {
show: true,
fontSize: "13px",
width: "12px",
textWrap: "wrap",
color: color,
fontWeight: 400,
offsetY: 7,
formatter: () =>
`${formatFigure(completed, {
notation: "compact",
})} / ${formatFigure(total, { notation: "compact" })}`,
},
style: {
textWrap: "wrap",
},
},
},
},
stroke: { lineCap: "round" },
};
return (
<div style={{ width: "fit-content" }}>
<Chart
options={options}
series={[Number(series)]}
type="radialBar"
height={height}
width={width}
/>
</div>
);
};
export default Progress;
// const Progress = ({
// completed = 0,
// inProgress = 0,
// total = 100,
// height = 140,
// width = 140,
// }) => {
// const completedPercent =
// total > 0 ? Math.round((completed / total) * 100) : 0;
// const progressPercent =
// total > 0 ? Math.round((completed / total) * 100) : 0;
// const options = {
// chart: {
// type: "radialBar",
// },
// colors: ["#28a745", "#0d6efd"],
// plotOptions: {
// radialBar: {
// hollow: {
// size: "35%",
// },
// track: {
// margin: 4,
// },
// dataLabels: {
// name: { show: false },
// value: {
// show: true,
// fontSize: "14px",
// fontWeight: 600,
// offsetY: 5,
// formatter: () => `${completed + inProgress}/${total}`,
// },
// },
// },
// },
// labels: ["Completed", "In Progress"],
// stroke: {
// lineCap: "round",
// },
// };
// return (
// <div style={{ width: "fit-content" }}>
// <Chart
// options={options}
// series={[completedPercent, progressPercent]}
// type="radialBar"
// height={height}
// width={width}
// />
// </div>
// );
// };

View File

@ -0,0 +1,65 @@
import { getCompletionPercentage } from "../../utils/dateUtils";
import Progress from "./Progress";
import ReportsLegend from "./ReportsLegend";
const ReportsDonutCard = ({
title,
value,
total,
donutClass = "",
footer = "Team members present on the site",
chartColor,
legend1 = "Completed",
legend2 = "Pending",
}) => {
return (
<div className="border-top card border-primary py-4 px-2 h-100">
<h4 className="reports-card-title">{title}</h4>
<div className="d-flex justify-content-center align-items-center flex-column">
<div className="d-inline-flex flex-row align-items-center gap-12 gap-md-3">
<Progress
color={chartColor}
width={120}
height={120}
series={getCompletionPercentage(value, total)}
completed={value}
total={total}
/>
<ReportsLegend legend1={legend1} legend2={legend2} />
</div>
</div>
<div className="text-center p-2">
<p className="text-muted mb-0">{footer}</p>
</div>
</div>
);
};
export default ReportsDonutCard;
export const ReportsCard = ({
title,
value,
total,
donutClass = "",
footer = "Team members present on the site",
chartColor,
}) => {
return (
<div className="border-top card border-primary py-4 px-2">
<h4 className="reports-card-title">{title}</h4>
<div className="d-flex justify-content-start align-items-center flex-column">
<div className="d-inline-flex flex-row align-items-center gap-12 gap-md-3">
<ReportsLegend />
</div>
</div>
<div className="text-center p-2">
<p className="text-muted mb-0">{footer}</p>
</div>
</div>
);
};

View File

@ -0,0 +1,17 @@
const ReportsLegend = ({legend1, legend2}) => {
return (
<div className=" d-inline-flex flex-column text-start gap-2 pe-5">
<div className=" d-flex align-items-center gap-2">
<span className="reports-legend-color reports-legend-green"></span>
<span>{legend1}</span>
</div>
<div className=" d-flex align-items-center gap-2">
<span className="reports-legend-color reports-legend-blue"></span>
<span>{legend2}</span>
</div>
</div>
);
};
export default ReportsLegend;

View File

@ -0,0 +1,20 @@
const TeamStrengthCard = ({ data }) => {
return (
<div className="reports-card">
<h4 className="reports-card-title">Team Strength on Site</h4>
<table style={{ width: "100%" }}>
<tbody>
{data.map((item, i) => (
<tr key={i}>
<td style={{ textAlign: "left" }}>{item.role}</td>
<td style={{ textAlign: "right" }}>{item.count}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default TeamStrengthCard;

View File

@ -0,0 +1,176 @@
import React from "react";
import { useProjectReportByProject } from "../../hooks/useReports";
import Progress from "./Progress";
import { formatUTCToLocalTime } from "../../utils/dateUtils";
import { localToUtc } from "../../utils/appUtils";
import ReportsDonutCard, { ReportsCard } from "./ReportsDonutCard";
import { SpinnerLoader } from "../common/Loader";
const ReportDPR = ({ project, date }) => {
const { data, isLoading, isError, error } = useProjectReportByProject(
project,
localToUtc(date)
);
return (
<>
<div className="card py-2 mb-5">
<div className="d-flex text-start px-2 mt-2">
Project Status Reported - Generated at{" "}
{formatUTCToLocalTime(data?.date, true)}
</div>
{/* <!-- Status Cards */}
<div className="row px-5 py-4">
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
{" "}
<ReportsDonutCard
title={"TODAY'S ATTENDANCE"}
total={data?.totalEmployees}
value={data?.todaysAttendances}
legend1="Today's Attendance"
legend2="Total Employees"
/>
</div>
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
{" "}
<ReportsDonutCard
title={"DAILY TASKS COMPLETED"}
total={data?.totalCompletedTask}
value={data?.totalPlannedWork}
chartColor={"blue"}
footer=""
legend1="Completed Work"
legend2="Planned Work"
/>
</div>
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
{" "}
<ReportsDonutCard
title={"PROJECT COMPLETION STATUS"}
total={data?.totalPlannedWork}
value={data?.totalCompletedWork}
chartColor={"green"}
footer=""
legend1="Completed Work"
legend2="Planned Work"
/>
</div>
<div className="col-6 col-md-3" style={{ minHeight: "255px" }}>
<div className="border-top card border-warning py-4 px-2 h-100">
<h5 className="reports-card-title">Attendance Pending Report</h5>
<div className="d-flex flex-column gap-2">
<div className="d-flex justify-content-between">
<span className="text-secondry"> Regualrization</span>{" "}
<span className="text-secondry">
{" "}
{data?.regularizationPending}
</span>{" "}
</div>
<div className="d-flex justify-content-between">
<span className="text-secondry"> Checking</span>{" "}
<span className="text-secondry">
{" "}
{data?.checkoutPending}
</span>{" "}
</div>
<div className="d-flex justify-content-between">
<span className="text-secondry"> Total Employee</span>{" "}
<span className="text-secondry">
{" "}
{data?.todaysAttendances}
</span>{" "}
</div>
</div>
</div>
</div>
</div>
<div className="row px-5 pb-5">
<div className="col-4">
<div className="reports-card h-100" style={{ minHeight: "250px" }}>
{/* {/* <!-- Row 1: Header */}
<div>
<h4 className="reports-card-title">Team Strength on Site</h4>
</div>
<table style={{ width: "100%" }}>
<tbody>
{data?.teamOnSite?.filter(
(item) => item?.numberofEmployees > 0
).length > 0 ? (
data?.teamOnSite
?.filter((item) => item?.numberofEmployees > 0)
?.map((member, index) => (
<tr key={index}>
<td style={{ textAlign: "left" }}>
{member?.roleName}
</td>
<td style={{ textAlign: "right" }}>
{member?.numberofEmployees}
</td>
</tr>
))
) : (
<tr>
<td colSpan="2" style={{ textAlign: "center" }}>
No Records Available
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
<div className="col-8">
{/* {/* <!-- Activities */}
<div className="reports-card h-100" style={{ minHeight: "250px" }}>
<h4 className="reports-card-title">Employee In-Out Report </h4>
<table className="reports-table">
<thead>
<tr>
<th>NAME</th>
<th>JOB ROLE</th>
<th>CHECK IN</th>
<th>CHECK OUT</th>
</tr>
</thead>
<tbody>
{data?.performedAttendance?.length > 0 ? (
data?.performedAttendance?.map((att, index) => (
<tr key={att.index + att.name}>
<td>{att.name}</td>
<td>{att.roleName}</td>
<td>{formatUTCToLocalTime(att.inTime, true)}</td>
<td>
{att.outTime
? formatUTCToLocalTime(att.outTime, true)
: "--"}
</td>
</tr>
))
) : (
<tr>
<td
colSpan="4"
className="text-center py-4 border-0"
style={{ height: "200px" }}
>
{isLoading ? (
<SpinnerLoader />
) : (
<div className="py-8">No Records Available</div>
)}
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
</>
);
};
export default ReportDPR;

View File

@ -1,6 +1,7 @@
import { useForm, Controller, FormProvider, useFormContext } from "react-hook-form"; import { useForm, Controller, FormProvider, useFormContext, useWatch } from "react-hook-form";
export const useAppForm = (config) => useForm(config); export const useAppForm = (config) => useForm(config);
export const AppFormProvider = FormProvider; export const AppFormProvider = FormProvider;
export const AppFormController = Controller; export const AppFormController = Controller;
export const useAppFormContext = useFormContext; export const useAppFormContext = useFormContext;
export const useAppWatch = useWatch;

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import GlobalRepository from "../repositories/GlobalRepository"; import GlobalRepository from "../repositories/GlobalRepository";
import { useQuery } from "@tanstack/react-query"; import { keepPreviousData, useQuery } from "@tanstack/react-query";
export const useDashboard_Data = ({ days, FromDate, projectId }) => { export const useDashboard_Data = ({ days, FromDate, projectId }) => {
const [dashboard_data, setDashboard_Data] = useState([]); const [dashboard_data, setDashboard_Data] = useState([]);
@ -212,3 +212,16 @@ export const useJobsProgression = (projectId) => {
}, },
}); });
}; };
export const useAttendaceProjectWiseOveriew = (date) => {
return useQuery({
queryKey: ["attendaceOverview", date],
queryFn: async () => {
const resp = await GlobalRepository.getAttendanceProjectWiseOverview(
date
);
return resp.data;
},
keepPreviousData: true,
});
};

16
src/hooks/useReports.js Normal file
View File

@ -0,0 +1,16 @@
import { useQuery } from "@tanstack/react-query";
import { ReportsRepository } from "../repositories/GlobalRepository";
export const useProjectReportByProject = (projectId, date) => {
return useQuery({
queryKey: ["daily_report", projectId, date],
queryFn: async () => {
const resp = await ReportsRepository.getDailyProgressByProject(
projectId,
date
);
return resp.data;
},
enabled: !!projectId && !!date,
});
};

View File

@ -122,20 +122,29 @@ const AttendancePage = () => {
// Conditionally include Regularization tab based on permission // Conditionally include Regularization tab based on permission
...(DoRegularized ...(DoRegularized
? [ ? [
{ {
id: "regularization", id: "regularization",
title: "Regularization", title: "Regularization",
content: ( content: (
<Regularization <Regularization
searchTerm={searchTerm} searchTerm={searchTerm}
organizationId={appliedFilters.selectedOrganization} organizationId={appliedFilters.selectedOrganization}
/> />
), ),
}, },
] ]
: []), : []),
]; ];
// --- END: Tab Configuration Array // --- END: Tab Configuration Array
useEffect(() => {
if (!orgLoading && organizations?.length === 1) {
setAppliedFilters((prev) => ({
...prev,
selectedOrganization: organizations[0].id,
}));
}
}, [orgLoading, organizations]);
return ( return (
<> <>
@ -148,11 +157,11 @@ const AttendancePage = () => {
{(modelConfig?.action === 0 || {(modelConfig?.action === 0 ||
modelConfig?.action === 1 || modelConfig?.action === 1 ||
modelConfig?.action === 2) && ( modelConfig?.action === 2) && (
<CheckCheckOutmodel <CheckCheckOutmodel
modeldata={modelConfig} modeldata={modelConfig}
closeModal={closeModal} closeModal={closeModal}
/> />
)} )}
{/* For view logs */} {/* For view logs */}
{modelConfig?.action === 6 && ( {modelConfig?.action === 6 && (
<AttendLogs Id={modelConfig?.id} closeModal={closeModal} /> <AttendLogs Id={modelConfig?.id} closeModal={closeModal} />
@ -178,18 +187,16 @@ const AttendancePage = () => {
<ul className="nav nav-tabs" role="tablist"> <ul className="nav nav-tabs" role="tablist">
{tabsData.map((tab) => ( {tabsData.map((tab) => (
<li <li
className={`nav-item ${ className={`nav-item ${tab.id === "regularization" && !DoRegularized
tab.id === "regularization" && !DoRegularized ? "d-none"
? "d-none" : ""
: "" }`} // Keep the d-none logic for regularization, although it's filtered above
}`} // Keep the d-none logic for regularization, although it's filtered above
key={tab.id} key={tab.id}
> >
<button <button
type="button" type="button"
className={`nav-link ${ className={`nav-link ${activeTab === tab.id ? "active" : ""
activeTab === tab.id ? "active" : "" } fs-6`}
} fs-6`}
onClick={() => handleTabChange(tab.id)} onClick={() => handleTabChange(tab.id)}
data-bs-toggle="tab" data-bs-toggle="tab"
data-bs-target={`#navs-top-${tab.id}`} data-bs-target={`#navs-top-${tab.id}`}
@ -245,9 +252,8 @@ const AttendancePage = () => {
{tabsData.map((tab) => ( {tabsData.map((tab) => (
<div <div
key={tab.id} key={tab.id}
className={`tab-pane fade ${ className={`tab-pane fade ${activeTab === tab.id ? "show active" : ""
activeTab === tab.id ? "show active" : "" } py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`}
} py-0 ${tab.id === "all" ? "mx-2" : "p-3"}`}
id={`navs-top-${tab.id}`} id={`navs-top-${tab.id}`}
role="tabpanel" role="tabpanel"
> >

View File

@ -15,7 +15,7 @@ const TaskPlanning = () => {
const selectedProject = useSelectedProject(); const selectedProject = useSelectedProject();
const selectedService = useCurrentService(); const selectedService = useCurrentService();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { control } = useForm({ const { control,setValue } = useForm({
defaultValues: { defaultValues: {
serviceFilter: selectedService ?? "" serviceFilter: selectedService ?? ""
}, },
@ -39,6 +39,20 @@ const TaskPlanning = () => {
return <div className="text-center py-5">Loading...</div>; return <div className="text-center py-5">Loading...</div>;
} }
useEffect(() => {
if (!servicesLoading && data?.length === 1) {
const serviceId = data[0].id;
setValue("serviceFilter", serviceId, {
shouldValidate: true,
shouldDirty: true,
});
dispatch(setService(serviceId));
}
}, [servicesLoading, data, setValue, dispatch]);
return ( return (
<div className="container-fluid"> <div className="container-fluid">
<Breadcrumb <Breadcrumb
@ -51,7 +65,7 @@ const TaskPlanning = () => {
<div className="card py-2 page-min-h"> <div className="card py-2 page-min-h">
<div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start"> <div className="col-sm-4 col-md-3 col-12 px-4 py-2 text-start">
{data?.length === 0 ? ( {data?.length === 0 ? (
<p className="badge bg-label-secondary m-0">Service not assigned</p> <p className="badge bg-label-secondary m-0"></p>
) : ( ) : (
<AppFormController <AppFormController
name="serviceFilter" name="serviceFilter"
@ -78,6 +92,7 @@ const TaskPlanning = () => {
{/* Planning Component */} {/* Planning Component */}
{selectedProject ? ( {selectedProject ? (
<InfraPlanning /> <InfraPlanning />

View File

@ -1,4 +1,4 @@
import React, { createContext, useContext, useEffect, useState } from "react"; import React, { createContext, useContext, useEffect, useRef, useState } from "react";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import { useServices } from "../../hooks/masterHook/useMaster"; import { useServices } from "../../hooks/masterHook/useMaster";
import TaskReportList from "../../components/DailyProgressRport/TaskReportList"; import TaskReportList from "../../components/DailyProgressRport/TaskReportList";
@ -13,6 +13,7 @@ import { useSelectedProject } from "../../slices/apiDataManager";
import SelectField from "../../components/common/Forms/SelectField"; import SelectField from "../../components/common/Forms/SelectField";
import { AppFormController } from "../../hooks/appHooks/useAppForm"; import { AppFormController } from "../../hooks/appHooks/useAppForm";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { TaskReportDefaultValue } from "../../components/DailyProgressRport/TaskRportScheam";
const DailyProgrssContext = createContext(); const DailyProgrssContext = createContext();
export const useDailyProgrssContext = () => { export const useDailyProgrssContext = () => {
@ -30,8 +31,9 @@ const DailyProgrssReport = () => {
const [service, setService] = useState(""); const [service, setService] = useState("");
const [filter, setFilter] = useState('') const [filter, setFilter] = useState('')
const { setOffcanvasContent, setShowTrigger } = useFab(); const { setOffcanvasContent, setShowTrigger } = useFab();
const updatedRef = useRef();
const { data, isLoading, isError, error } = useProjectAssignedServices(selectedProject); const { data, isLoading, isError, error } = useProjectAssignedServices(selectedProject);
const [filterData, setFilterdata] = useState(null);
const [modal, setModal] = useState({ type: null, data: null }); const [modal, setModal] = useState({ type: null, data: null });
const openModal = (type, data = null) => setModal({ type, data }); const openModal = (type, data = null) => setModal({ type, data });
@ -44,25 +46,61 @@ const DailyProgrssReport = () => {
filter, filter,
}; };
const { control } = useForm({ const { control,setValue } = useForm({
defaultValues: { defaultValues: {
serviceFilter: "" serviceFilter: ""
} }
}); });
const clearFilter = () => {
updatedRef.current?.onClear();
};
const handleFilter = (filterObj) => { const handleFilter = (filterObj) => {
setFilter(filterObj) setFilter(filterObj)
} }
useEffect(() => { useEffect(() => {
setShowTrigger(true); setShowTrigger(true);
setOffcanvasContent("Report Filter", <TaskReportFilterPanel handleFilter={handleFilter} />); setOffcanvasContent("Report Filter", <TaskReportFilterPanel handleFilter={handleFilter} ref={updatedRef}
clearFilter={clearFilter}
setFilterdata={setFilterdata} />);
return () => { return () => {
setShowTrigger(false); setShowTrigger(false);
setOffcanvasContent("", null); setOffcanvasContent("", null);
}; };
}, []); }, []);
const handleRemoveChip = (key, id) => {
setFilter((prev) => {
const updated = { ...prev };
if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id);
setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0);
} else {
updated[key] = null;
setTimeout(() => updatedRef.current?.resetFieldValue(key, null), 0);
}
return updated;
});
};
useEffect(() => {
if (!isLoading && data?.length === 1) {
const serviceId = data[0].id;
// set form value
setValue("serviceFilter", serviceId, {
shouldDirty: true,
shouldValidate: true,
});
// set local state
setService(serviceId);
}
}, [isLoading, data, setValue]);
return ( return (
<div className="container-fluid"> <div className="container-fluid">
<DailyProgrssContext.Provider value={contextDispatcher}> <DailyProgrssContext.Provider value={contextDispatcher}>
@ -97,7 +135,7 @@ const DailyProgrssReport = () => {
]} ]}
/> />
<div className="card card-fullscreen p-5"> <div className="card page-min-h p-5">
{data?.length > 0 && ( {data?.length > 0 && (
<div className="col-sm-4 col-md-3 col-12 text-start"> <div className="col-sm-4 col-md-3 col-12 text-start">
<AppFormController <AppFormController
@ -107,7 +145,7 @@ const DailyProgrssReport = () => {
render={({ field }) => ( render={({ field }) => (
<SelectField <SelectField
label="Services" label="Services"
options={[{ id: "", name: "All Projects" }, ...(data ?? [])]} options={[{ id: "", name: "All Services" }, ...(data ?? [])]}
placeholder="Select Service" placeholder="Select Service"
labelKey="name" labelKey="name"
valueKey="id" valueKey="id"
@ -125,7 +163,10 @@ const DailyProgrssReport = () => {
)} )}
<div> <div>
<TaskReportList /> <TaskReportList filter={filter}
filterData={filterData}
removeFilterChip={handleRemoveChip}
clearFilter={clearFilter} />
</div> </div>
</div> </div>

View File

@ -106,10 +106,13 @@ const ExpensePage = () => {
updated[key] = updated[key].filter((v) => v !== id); updated[key] = updated[key].filter((v) => v !== id);
filterPanelRef.current?.resetFieldValue(key, updated[key]); filterPanelRef.current?.resetFieldValue(key, updated[key]);
} else if (key === "dateRange") { } else if (key === "dateRange") {
// --- START FIX: Use a dedicated function to reset the date range ---
updated.startDate = null; updated.startDate = null;
updated.endDate = null; updated.endDate = null;
filterPanelRef.current?.resetFieldValue("startDate", null);
filterPanelRef.current?.resetFieldValue("endDate", null); // Call the new resetDateRange method on the ref
filterPanelRef.current?.resetDateRange();
// --- END FIX ---
} }
return updated; return updated;
}); });

View File

@ -86,7 +86,7 @@ const LandingPage = () => {
<li className="nav-item ms-1"> <li className="nav-item ms-1">
<a <a
className="btn btn-sm btn-green btn-ovel-small px-3 my-1" className="btn btn-sm btn-green btn-ovel-small px-3 my-1"
href="#" href="/auth/reqest/demo"
> >
Request For Demo Request For Demo
</a> </a>
@ -162,7 +162,7 @@ const LandingPage = () => {
Make data-driven decisions with real-time project analytics. Make data-driven decisions with real-time project analytics.
</p> </p>
<a <a
href="#" href="/auth/reqest/demo"
className="btn btn-green btn-square-small btn-lg mt-3 p-3" className="btn btn-green btn-square-small btn-lg mt-3 p-3"
> >
View Demo View Demo
@ -185,7 +185,7 @@ const LandingPage = () => {
Eliminate Paper Receipts. Take Control of Your Cash Flow. Eliminate Paper Receipts. Take Control of Your Cash Flow.
</p> </p>
<a <a
href="#" href="/auth/reqest/demo"
className="btn btn-green btn-square-small btn-lg mt-3 p-3" className="btn btn-green btn-square-small btn-lg mt-3 p-3"
> >
View Demo View Demo
@ -320,65 +320,6 @@ const LandingPage = () => {
</p> </p>
{/* <SubscriptionPlans/> */} {/* <SubscriptionPlans/> */}
<SubscriptionPlans></SubscriptionPlans> <SubscriptionPlans></SubscriptionPlans>
{/* <div className="row g-4 justify-content-center hidden">
<div className="col-md-4">
<div className="card pricing-card border-0 shadow-sm">
<div className="card-body">
<h5 className="text-green fw-bold">Starter</h5>
<h2>
499<span className="fs-6 text-muted">/month</span>
</h2>
<ul className="list-unstyled mt-3 mb-4 text-muted">
<li>Up to 10 users</li>
<li>Basic reporting</li>
<li>Project tracking</li>
</ul>
<a href="#" className="btn btn-outline-success">
Request Demo
</a>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card pricing-card border-primary shadow-lg">
<div className="card-body">
<h5 className="text-green fw-bold">Professional</h5>
<h2>
999<span className="fs-6 text-muted">/month</span>
</h2>
<ul className="list-unstyled mt-3 mb-4 text-muted">
<li>Unlimited projects</li>
<li>Expense & attendance tracking</li>
<li>Advanced analytics</li>
</ul>
<a
href="https://ofw.marcoaiot.com/auth/reqest/demo"
className="btn btn-green btn-square-small"
>
Request Demo
</a>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card pricing-card border-0 shadow-sm">
<div className="card-body">
<h5 className="text-green fw-bold">Enterprise</h5>
<h2>Custom</h2>
<ul className="list-unstyled mt-3 mb-4 text-muted">
<li>Dedicated support</li>
<li>Custom integrations</li>
<li>Private cloud hosting</li>
</ul>
<a href="#" className="btn btn-outline-success">
Request Demo
</a>
</div>
</div>
</div>
</div> */}
</div> </div>
</section> </section>
{/* <!-- About --> */} {/* <!-- About --> */}

View File

@ -9,7 +9,8 @@ const SubscriptionPlans = () => {
const [frequency, setFrequency] = useState(1); const [frequency, setFrequency] = useState(1);
const { data, isLoading, isError, error } = useSubscription(frequency); const { data, isLoading, isError, error } = useSubscription(frequency);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(true);
console.log(data);
const frequencyLabel = (freq) => { const frequencyLabel = (freq) => {
switch (freq) { switch (freq) {
case 0: case 0:
@ -35,9 +36,8 @@ const SubscriptionPlans = () => {
<button <button
key={idx} key={idx}
type="button" type="button"
className={`btn btn-${ className={`btn btn-${frequency === idx ? "primary" : "outline-secondary"
frequency === idx ? "primary" : "outline-secondary" }`}
}`}
onClick={() => setFrequency(idx)} onClick={() => setFrequency(idx)}
> >
{label} {label}
@ -64,7 +64,7 @@ const SubscriptionPlans = () => {
<p>{error.name}</p> <p>{error.name}</p>
</div> </div>
) : ( ) : (
data.map((plan) => ( data.map((plan, index) => (
<div key={plan.id} className="col-xl-4 col-lg-6 col-md-6"> <div key={plan.id} className="col-xl-4 col-lg-6 col-md-6">
<div className="card h-100 shadow-lg border-0 p-3 text-center p-10"> <div className="card h-100 shadow-lg border-0 p-3 text-center p-10">
{/* Header */} {/* Header */}
@ -102,24 +102,69 @@ const SubscriptionPlans = () => {
<h6 className="fw-bold text-uppercase border-top pt-3 mb-3 text-center"> <h6 className="fw-bold text-uppercase border-top pt-3 mb-3 text-center">
Features Features
</h6> </h6>
<ul className="list-unstyled text-start mb-4 ms-7 fs-6">
<div className="accordion" id={`planFeatures-${plan.id}`}>
{plan.features?.modules && {plan.features?.modules &&
Object.values(plan.features.modules).map((mod) => Object.entries(plan.features.modules)
mod && mod.name ? ( .sort(([, a], [, b]) => Number(b.enabled) - Number(a.enabled))
<li .map(([key, mod]) => {
key={mod.id}
className="d-flex align-items-center mb-2" if (!mod || !mod.name) return null;
> const isFirst = index === 0;
{mod.enabled ? (
<i className="fa-regular fa-circle-check text-success me-2"></i> return (
) : ( <div className="accordion-item mb-2" key={`${plan.id}-${mod.id}`}>
<i className="fa-regular fa-circle-xmark text-danger me-2"></i> <h2 id={`heading-${plan.id}-${mod.id}`} className="accordion-header">
)} <button
{mod.name} className="accordion-button py-2 d-flex justify-content-between align-items-center"
</li> type="button"
) : null data-bs-toggle="collapse"
)} data-bs-target={`#collapse-${plan.id}-${mod.id}`}
</ul> aria-expanded={`${isFirst ? "true" : "false"}`}
aria-controls={`collapse-${plan.id}-${mod.id}`}
>
<span className="fw-semibold d-flex align-items-center">
{mod.enabled ? (
<i className="fa-regular fa-circle-check text-success me-2"></i>
) : (
<i className="fa-regular fa-circle-xmark text-danger me-2"></i>
)}
{mod.name}
</span>
<i className="bx bx-chevron-down ms-2"></i>
</button>
</h2>
<div
id={`collapse-${plan.id}-${mod.id}`}
className={`accordion-collapse collapse ${isFirst ? "show" : ""}`}
aria-labelledby={`heading-${plan.id}-${mod.id}`}
>
<div className="accordion-body py-2">
{mod.features?.length > 0 ? (
<ul className="list-unstyled ms-2">
{mod.features.map((feat) => (
<li key={feat.id} className="d-flex align-items-start mb-1">
<i
className={`bx bxs-circle ${mod.enabled ? "text-success" : "text-danger"
} me-2 mt-1`}
style={{ fontSize: "8px" }}
></i>
<span>{feat.name}</span>
</li>
))}
</ul>
) : (
<p className="text-muted small mb-0">No additional features</p>
)}
</div>
</div>
</div>
);
})}
</div>
{/* Button */} {/* Button */}
<div className="mt-auto"> <div className="mt-auto">
@ -138,6 +183,7 @@ const SubscriptionPlans = () => {
</div> </div>
</div> </div>
</div> </div>
)) ))
)} )}
</div> </div>

View File

@ -71,6 +71,11 @@ const PaymentRequestPage = () => {
if (Array.isArray(updated[key])) { if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id); updated[key] = updated[key].filter((v) => v !== id);
setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0); setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0);
} else if (key === "dateRange") { // Handle date range reset
updated.startDate = null;
updated.endDate = null;
// Call the new resetDateRange method on the ref
updatedRef.current?.resetDateRange();
} else { } else {
updated[key] = null; updated[key] = null;
setTimeout(() => updatedRef.current?.resetFieldValue(key, null), 0); setTimeout(() => updatedRef.current?.resetFieldValue(key, null), 0);
@ -80,9 +85,9 @@ const PaymentRequestPage = () => {
}); });
}; };
const handleExport = (type) => { const handleExport = (type) => {
HandlePaymentRequestExport(type, filters, search, tableRef, setExportLoading); HandlePaymentRequestExport(type, filters, search, tableRef, setExportLoading);
}; };
return ( return (
<PaymentRequestContext.Provider value={contextValue}> <PaymentRequestContext.Provider value={contextValue}>

View File

@ -20,7 +20,7 @@ const ServiceProjectDisplay = ({ listView, selectedStatuses, searchTerm }) => {
const { manageServiceProject, setManageServiceProject } = useProjectContext(); const { manageServiceProject, setManageServiceProject } = useProjectContext();
const debouncedSearch = useDebounce(searchTerm, 500); const debouncedSearch = useDebounce(searchTerm, 500);
const { data, isLoading, isError, error } = useServiceProjects( const { data, isLoading, isError, error } = useServiceProjects(
ITEMS_PER_PAGE, 9,
currentPage, currentPage,
debouncedSearch debouncedSearch
); );

View File

@ -5,6 +5,7 @@ import React, {
useContext, useContext,
useCallback, useCallback,
useMemo, useMemo,
useRef,
} from "react"; } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
@ -59,6 +60,7 @@ const TenantPage = () => {
const [refetchFn, setRefetchFn] = useState(null); const [refetchFn, setRefetchFn] = useState(null);
const [filters, setFilters] = useState(); const [filters, setFilters] = useState();
// ---------- Hooks ---------- // ---------- Hooks ----------
const debouncedSearch = useDebounce(searchText, 500); const debouncedSearch = useDebounce(searchText, 500);
const { setOffcanvasContent, setShowTrigger } = useFab(); const { setOffcanvasContent, setShowTrigger } = useFab();
@ -66,6 +68,9 @@ const TenantPage = () => {
const isSuperTenant = useHasUserPermission(SUPPER_TENANT); const isSuperTenant = useHasUserPermission(SUPPER_TENANT);
const canManageTenants = useHasUserPermission(MANAGE_TENANTS); const canManageTenants = useHasUserPermission(MANAGE_TENANTS);
const isSelfTenant = useHasUserPermission(VIEW_TENANTS); const isSelfTenant = useHasUserPermission(VIEW_TENANTS);
const updatedRef = useRef();
const [filterData, setFilterdata] = useState(null);
const [filter, setFilter] = useState('')
const methods = useForm({ const methods = useForm({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),
@ -77,8 +82,18 @@ const TenantPage = () => {
setFilters(values); setFilters(values);
}, []); }, []);
const clearFilter = () => {
setFilters(defaultFilterValues); // update active filters
updatedRef.current?.resetFields?.(); // reset RHF fields in panel
};
const filterPanelElement = useMemo( const filterPanelElement = useMemo(
() => <TenantFilterPanel onApply={handleApplyFilters} />, () => <TenantFilterPanel
onApply={handleApplyFilters}
ref={updatedRef}
clearFilter={clearFilter}
setFilterdata={setFilterdata}
/>,
[handleApplyFilters] [handleApplyFilters]
); );
// ---------- Fab Filter Panel ---------- // ---------- Fab Filter Panel ----------
@ -112,6 +127,20 @@ const TenantPage = () => {
// ---------- Context Value ---------- // ---------- Context Value ----------
const contextValue = {}; const contextValue = {};
const handleRemoveChip = (key, id) => {
setFilters((prev) => {
const updated = { ...prev };
if (Array.isArray(updated[key])) {
updated[key] = updated[key].filter((v) => v !== id);
} else {
updated[key] = null;
}
setTimeout(() => updatedRef.current?.resetFieldValue(key, updated[key]), 0);
return updated;
});
};
return ( return (
<TenantContext.Provider value={contextValue}> <TenantContext.Provider value={contextValue}>
<div className="container-fluid"> <div className="container-fluid">
@ -121,7 +150,7 @@ const TenantPage = () => {
{ label: "Tenant", link: null }, { label: "Tenant", link: null },
]} ]}
/> />
<div className="card text-center my-4 p-md-5 px-1 py-3 pb-10"> <div className="card page-min-h text-center my-4 p-md-5 px-1 py-3 pb-10">
{/* Super Tenant Actions */} {/* Super Tenant Actions */}
{isSuperTenant && ( {isSuperTenant && (
<div className="p-0"> <div className="p-0">
@ -157,10 +186,13 @@ const TenantPage = () => {
{/* Tenant List or Access Denied */} {/* Tenant List or Access Denied */}
{isSuperTenant ? ( {isSuperTenant ? (
<TenantsList <TenantsList
setRefetchFn={setRefetchFn}
setIsRefetching={setIsRefetching}
filters={filters} filters={filters}
searchText={debouncedSearch} searchText={debouncedSearch}
setIsRefetching={setIsRefetching} filterData={filterData}
setRefetchFn={setRefetchFn} removeFilterChip={handleRemoveChip}
clearFilter={clearFilter}
/> />
) : !isSelfTenant ? ( ) : !isSelfTenant ? (
<div className="text-center my-4 p-2"> <div className="text-center my-4 p-2">

View File

@ -1,5 +1,5 @@
import React from 'react' import React from "react";
import LoginWithOtp from './LoginWithOtp' import LoginWithOtp from "./LoginWithOtp";
const MainLoginWithOTPPage = () => { const MainLoginWithOTPPage = () => {
return ( return (
@ -8,7 +8,7 @@ const MainLoginWithOTPPage = () => {
<div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5"> <div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5">
<div className="w-100 d-flex justify-content-center"> <div className="w-100 d-flex justify-content-center">
<img <img
src="/img/illustrations/worker_03.png" src="/img/illustrations/registration.jpg"
className="img-fluid" className="img-fluid"
alt="Login image" alt="Login image"
width="70%" width="70%"
@ -20,7 +20,7 @@ const MainLoginWithOTPPage = () => {
<LoginWithOtp /> <LoginWithOtp />
</div> </div>
</> </>
) );
} };
export default MainLoginWithOTPPage export default MainLoginWithOTPPage;

View File

@ -1,14 +1,14 @@
import React from 'react' import React from "react";
import ResetPasswordPage from './ResetPassword' import ResetPasswordPage from "./ResetPassword";
const MainResetPasswordPage = () => { const MainResetPasswordPage = () => {
return ( return (
<> <>
<div className="authentication-inner row m-0"> <div className="authentication-inner row m-0">
<div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5"> <div className="d-none d-lg-flex col-lg-7 col-xl-8 align-items-center p-5">
<div className="w-100 d-flex justify-content-center"> <div className="w-100 d-flex justify-content-center">
<img <img
src="/img/illustrations/worker_03.png" src="/img/illustrations/registration.jpg"
className="img-fluid" className="img-fluid"
alt="Login image" alt="Login image"
width="70%" width="70%"
@ -20,7 +20,7 @@ const MainResetPasswordPage = () => {
<ResetPasswordPage /> <ResetPasswordPage />
</div> </div>
</> </>
) );
} };
export default MainResetPasswordPage export default MainResetPasswordPage;

View File

@ -41,6 +41,7 @@ import Pagination from "../../components/common/Pagination";
import handleEmployeeExport from "../../components/Employee/handleEmployeeExport"; import handleEmployeeExport from "../../components/Employee/handleEmployeeExport";
import { SpinnerLoader } from "../../components/common/Loader"; import { SpinnerLoader } from "../../components/common/Loader";
import ManageReporting from "../../components/Employee/ManageReporting"; import ManageReporting from "../../components/Employee/ManageReporting";
import { capitalizeFirstLetter } from "../../utils/appUtils";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( const selectedProjectId = useSelector(
@ -73,9 +74,11 @@ const EmployeeList = () => {
const [selectedEmployee, setSelectedEmployee] = useState(null); const [selectedEmployee, setSelectedEmployee] = useState(null);
const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [IsDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null); const [selectedEmpFordelete, setSelectedEmpFordelete] = useState(null);
const [showManageReportingModal, setShowManageReportingModal] = useState(false); const [showManageReportingModal, setShowManageReportingModal] =
useState(false);
const [employeeLodaing, setemployeeLodaing] = useState(false); const [employeeLodaing, setemployeeLodaing] = useState(false);
const ViewTeamMember = useHasUserPermission(VIEW_TEAM_MEMBERS); const ViewTeamMember = useHasUserPermission(VIEW_TEAM_MEMBERS);
const ViewAllEmployee = useHasUserPermission(VIEW_ALL_EMPLOYEES);
const { mutate: suspendEmployee, isPending: empLodaing } = useSuspendEmployee( const { mutate: suspendEmployee, isPending: empLodaing } = useSuspendEmployee(
{ {
setIsDeleteModalOpen, setIsDeleteModalOpen,
@ -139,11 +142,15 @@ const EmployeeList = () => {
const tableRef = useRef(null); const tableRef = useRef(null);
const handleExport = (type) => { const handleExport = (type) => {
handleEmployeeExport(type, employeeList, filteredData, searchText, tableRef); handleEmployeeExport(
type,
employeeList,
filteredData,
searchText,
tableRef
);
}; };
const handleAllEmployeesToggle = (e) => { const handleAllEmployeesToggle = (e) => {
const isChecked = e.target.checked; const isChecked = e.target.checked;
setShowInactive(false); setShowInactive(false);
@ -172,10 +179,12 @@ const EmployeeList = () => {
useEffect(() => { useEffect(() => {
if (!loading && Array.isArray(employees)) { if (!loading && Array.isArray(employees)) {
const sorted = [...employees].sort((a, b) => { const sorted = [...employees].sort((a, b) => {
const nameA = `${a.firstName || ""}${a.middleName || ""}${a.lastName || "" const nameA = `${a.firstName || ""}${a.middleName || ""}${
}`.toLowerCase(); a.lastName || ""
const nameB = `${b.firstName || ""}${b.middleName || ""}${b.lastName || "" }`.toLowerCase();
}`.toLowerCase(); const nameB = `${b.firstName || ""}${b.middleName || ""}${
b.lastName || ""
}`.toLowerCase();
return nameA?.localeCompare(nameB); return nameA?.localeCompare(nameB);
}); });
@ -265,8 +274,9 @@ const EmployeeList = () => {
? "Suspend Employee" ? "Suspend Employee"
: "Reactivate Employee" : "Reactivate Employee"
} }
message={`Are you sure you want to ${selectedEmpFordelete?.isActive ? "suspend" : "reactivate" message={`Are you sure you want to ${
} this employee?`} selectedEmpFordelete?.isActive ? "suspend" : "reactivate"
} this employee?`}
onSubmit={(id) => onSubmit={(id) =>
suspendEmployee({ suspendEmployee({
employeeId: id, employeeId: id,
@ -287,7 +297,7 @@ const EmployeeList = () => {
]} ]}
></Breadcrumb> ></Breadcrumb>
{ViewTeamMember ? ( {ViewTeamMember || ViewAllEmployee ? (
// <div className="row"> // <div className="row">
<div className="card p-1 page-min-h"> <div className="card p-1 page-min-h">
<div className="card-datatable table-responsive pt-5 mx-5 py-10"> <div className="card-datatable table-responsive pt-5 mx-5 py-10">
@ -341,8 +351,10 @@ const EmployeeList = () => {
> >
<i className="bx bx-dots-vertical-rounded bx-md"></i> <i className="bx bx-dots-vertical-rounded bx-md"></i>
</button> </button>
<ul className="dropdown-menu dropdown-menu-end shadow-sm " style={{ minWidth: "220px" }}> <ul
className="dropdown-menu dropdown-menu-end shadow-sm "
style={{ minWidth: "220px" }}
>
<li className="dropdown-item d-flex align-items-center justify-content-between"> <li className="dropdown-item d-flex align-items-center justify-content-between">
<div className="form-check form-switch mb-0"> <div className="form-check form-switch mb-0">
<input <input
@ -351,39 +363,53 @@ const EmployeeList = () => {
role="switch" role="switch"
id="inactiveEmployeesCheckboxMenu" id="inactiveEmployeesCheckboxMenu"
checked={showInactive} checked={showInactive}
onChange={(e) => setShowInactive(e.target.checked)} onChange={(e) =>
setShowInactive(e.target.checked)
}
/> />
</div> </div>
<span className="ms-0">Show Inactive Employees</span> <span className="ms-0">Show Inactive Employees</span>
</li> </li>
<li>
<li><hr className="dropdown-divider" /></li> <hr className="dropdown-divider" />
</li>
<li> <li>
<button className="dropdown-item" onClick={() => handleExport("print")}> <button
className="dropdown-item"
onClick={() => handleExport("print")}
>
<i className="bx bx-printer me-2"></i> Print <i className="bx bx-printer me-2"></i> Print
</button> </button>
</li> </li>
<li> <li>
<button className="dropdown-item" onClick={() => handleExport("csv")}> <button
className="dropdown-item"
onClick={() => handleExport("csv")}
>
<i className="bx bx-file me-2"></i> CSV <i className="bx bx-file me-2"></i> CSV
</button> </button>
</li> </li>
<li> <li>
<button className="dropdown-item" onClick={() => handleExport("excel")}> <button
className="dropdown-item"
onClick={() => handleExport("excel")}
>
<i className="bx bxs-file-export me-2"></i> Excel <i className="bx bxs-file-export me-2"></i> Excel
</button> </button>
</li> </li>
<li> <li>
<button className="dropdown-item" onClick={() => handleExport("pdf")}> <button
className="dropdown-item"
onClick={() => handleExport("pdf")}
>
<i className="bx bxs-file-pdf me-2"></i> PDF <i className="bx bxs-file-pdf me-2"></i> PDF
</button> </button>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<table <table
@ -460,8 +486,9 @@ const EmployeeList = () => {
Status Status
</th> </th>
<th <th
className={`sorting_disabled ${!Manage_Employee && "d-none" className={`sorting_disabled ${
}`} !Manage_Employee && "d-none"
}`}
rowSpan="1" rowSpan="1"
colSpan="1" colSpan="1"
style={{ width: "50px" }} style={{ width: "50px" }}
@ -475,29 +502,25 @@ const EmployeeList = () => {
{loading && ( {loading && (
<tr> <tr>
<td colSpan={8} className="text-center py-5"> <td colSpan={8} className="text-center py-5">
<div className="d-flex justify-content-center align-items-center py-5" style={{ minHeight: "50vh" }}> <div
className="d-flex justify-content-center align-items-center py-5"
style={{ minHeight: "50vh" }}
>
<SpinnerLoader /> <SpinnerLoader />
</div> </div>
</td> </td>
</tr> </tr>
)} )}
{!loading && displayData?.length === 0 && !searchText ? (
{!loading &&
displayData?.length === 0 &&
(!searchText) ? (
<tr> <tr>
<td colSpan={8} className="border-0 py-3"> <td colSpan={8} className="border-0 py-3">
<div className="py-4"> <div className="py-4">No Data Found</div>
No Data Found
</div>
</td> </td>
</tr> </tr>
) : null} ) : null}
{!loading && {!loading && displayData?.length === 0 && searchText ? (
displayData?.length === 0 &&
(searchText) ? (
<tr> <tr>
<td colSpan={8} className="border-0 py-3"> <td colSpan={8} className="border-0 py-3">
<div className="py-4"> <div className="py-4">
@ -526,8 +549,9 @@ const EmployeeList = () => {
className="text-heading text-truncate cursor-pointer" className="text-heading text-truncate cursor-pointer"
> >
<span className="fw-normal"> <span className="fw-normal">
{item.firstName} {item.middleName}{" "} {capitalizeFirstLetter(item.firstName)}{" "}
{item.lastName} {capitalizeFirstLetter(item.middleName)}{" "}
{capitalizeFirstLetter(item.lastName)}
</span> </span>
</a> </a>
</div> </div>
@ -540,7 +564,9 @@ const EmployeeList = () => {
{item.email} {item.email}
</span> </span>
) : ( ) : (
<span className="d-block text-start text-muted fst-italic">NA</span> <small className="d-block text-start text-muted small">
-
</small>
)} )}
</td> </td>
@ -561,7 +587,9 @@ const EmployeeList = () => {
{item.joiningDate ? ( {item.joiningDate ? (
moment(item.joiningDate).format("DD-MMM-YYYY") moment(item.joiningDate).format("DD-MMM-YYYY")
) : ( ) : (
<span className="d-block text-center text-muted fst-italic">NA</span> <span className="d-block text-center text-muted fst-italic">
NA
</span>
)} )}
</td> </td>

View File

@ -1,4 +1,10 @@
import React, { createContext, useContext, useEffect, useRef, useState } from "react"; import React, {
createContext,
useContext,
useEffect,
useRef,
useState,
} from "react";
import Breadcrumb from "../../components/common/Breadcrumb"; import Breadcrumb from "../../components/common/Breadcrumb";
import { import {
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
@ -68,7 +74,7 @@ const ProjectPage = () => {
const handleToggleProject = (value) => { const handleToggleProject = (value) => {
setCoreProjects(value); setCoreProjects(value);
sessionStorage.setItem("whichProjectDisplay", String(value)); sessionStorage.setItem("whichProjectDisplay", value ? "true" : "false");
}; };
useEffect(() => { useEffect(() => {
@ -100,8 +106,9 @@ const ProjectPage = () => {
{/* Service Project Button */} {/* Service Project Button */}
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${!coreProjects ? "btn-primary text-white" : "" className={`btn px-2 py-1 rounded-0 text-tiny ${
}`} !coreProjects ? "btn-primary text-white" : ""
}`}
onClick={() => handleToggleProject(false)} onClick={() => handleToggleProject(false)}
> >
Service Project Service Project
@ -109,8 +116,9 @@ const ProjectPage = () => {
{/* Organization Project Button */} {/* Organization Project Button */}
<button <button
type="button" type="button"
className={`btn px-2 py-1 rounded-0 text-tiny ${coreProjects ? "btn-primary text-white" : "" className={`btn px-2 py-1 rounded-0 text-tiny ${
}`} coreProjects ? "btn-primary text-white" : ""
}`}
onClick={() => handleToggleProject(true)} onClick={() => handleToggleProject(true)}
> >
Infra Project Infra Project
@ -138,8 +146,9 @@ const ProjectPage = () => {
<div className="d-flex gap-2"> <div className="d-flex gap-2">
<button <button
type="button" type="button"
className={`btn btn-sm p-1 ${!listView ? "btn-primary" : "btn-outline-primary" className={`btn btn-sm p-1 ${
}`} !listView ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setListView(false)} onClick={() => setListView(false)}
title="Card View" title="Card View"
> >
@ -148,8 +157,9 @@ const ProjectPage = () => {
<button <button
type="button" type="button"
className={`btn btn-sm p-1 ${listView ? "btn-primary" : "btn-outline-primary" className={`btn btn-sm p-1 ${
}`} listView ? "btn-primary" : "btn-outline-primary"
}`}
onClick={() => setListView(true)} onClick={() => setListView(true)}
title="List View" title="List View"
> >
@ -164,12 +174,16 @@ const ProjectPage = () => {
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
> >
<i <i
className={`bx bx-slider-alt fs-5 ${selectedStatuses.length !== PROJECT_STATUS.length ? "text-primary" : "" className={`bx bx-slider-alt fs-5 ${
}`} selectedStatuses.length !== PROJECT_STATUS.length
? "text-primary"
: ""
}`}
></i> ></i>
{selectedStatuses.length !== PROJECT_STATUS.length && ( {selectedStatuses.length !== PROJECT_STATUS.length && (
<span className="badge bg-warning text-white rounded-pill position-absolute" <span
className="badge bg-warning text-white rounded-pill position-absolute"
style={{ style={{
top: "-4px", top: "-4px",
right: "-4px", right: "-4px",
@ -186,7 +200,7 @@ const ProjectPage = () => {
<ul <ul
ref={dropdownRef} ref={dropdownRef}
className="dropdown-menu show p-2 text-capitalize" className="dropdown-menu show p-2 text-capitalize"
onMouseDown={(e) => e.stopPropagation()} // IMPORTANT onMouseDown={(e) => e.stopPropagation()} // IMPORTANT
> >
{PROJECT_STATUS.map(({ id, label }) => ( {PROJECT_STATUS.map(({ id, label }) => (
<li key={id}> <li key={id}>
@ -200,7 +214,7 @@ const ProjectPage = () => {
/> />
<label <label
className="form-check-label" className="form-check-label"
onClick={(e) => e.stopPropagation()} // OPTIONAL onClick={(e) => e.stopPropagation()} // OPTIONAL
> >
{label} {label}
</label> </label>
@ -211,10 +225,7 @@ const ProjectPage = () => {
)} )}
</div> </div>
{(HasManageProject || !coreProjects) && (
{HasManageProject && (
<button <button
type="button" type="button"
className="btn btn-primary btn-sm d-flex align-items-center my-2" className="btn btn-primary btn-sm d-flex align-items-center my-2"
@ -223,9 +234,9 @@ const ProjectPage = () => {
coreProjects coreProjects
? setMangeProject({ isOpen: true, Project: null }) // Organization Project Infra ? setMangeProject({ isOpen: true, Project: null }) // Organization Project Infra
: setManageServiceProject({ : setManageServiceProject({
isOpen: true, isOpen: true,
Project: null, Project: null,
}) // Service Project }) // Service Project
} }
> >
<i className="bx bx-plus-circle me-2"></i> <i className="bx bx-plus-circle me-2"></i>

View File

@ -29,25 +29,11 @@ const ProjectsDisplay = ({
const [projectList, setProjectList] = useState([]); const [projectList, setProjectList] = useState([]);
const debouncedSearch = useDebounce(searchTerm, 500); const debouncedSearch = useDebounce(searchTerm, 500);
const { data, isLoading, isError, error } = useProjects( const { data, isLoading, isError, error } = useProjects(
ITEMS_PER_PAGE, 9,
currentPage, currentPage,
debouncedSearch debouncedSearch
); );
const filteredProjects =
data?.data?.filter((project) => {
const statusId =
project.projectStatusId ?? project?.status?.id ?? project?.statusId;
const matchesStatus = selectedStatuses.includes(statusId);
const matchesSearch = project?.name
?.toLowerCase()
?.includes(searchTerm?.toLowerCase());
return matchesStatus && matchesSearch;
}) ?? [];
const paginate = (page) => { const paginate = (page) => {
if (page >= 1 && page <= (data?.totalPages ?? 1)) { if (page >= 1 && page <= (data?.totalPages ?? 1)) {
setCurrentPage(page); setCurrentPage(page);
@ -68,7 +54,7 @@ const ProjectsDisplay = ({
.filter((statusId) => grouped[statusId]) .filter((statusId) => grouped[statusId])
.flatMap((statusId) => .flatMap((statusId) =>
grouped[statusId].sort((a, b) => grouped[statusId].sort((a, b) =>
a?.name?.toLowerCase()?.localeCompare(b?.name?.toLowerCase()) a?.shortName?.toLowerCase()?.localeCompare(b?.shortName?.toLowerCase())
) )
); );

View File

@ -0,0 +1,282 @@
/* .reports-container {
margin: 20px auto;
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
} */
.reports-header {
color: #fff;
padding: 20px;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.reports-header h1 {
font-size: 22px;
margin: 0;
}
.reports-header .project-info {
font-size: 14px;
text-align: right;
}
.reports-status-note {
font-size: 12px;
color: #555;
padding: 15px 20px 0 20px;
}
.reports-status-cards {
display: flex;
justify-content: space-between;
gap: 15px;
padding: 20px;
flex-wrap: wrap;
}
.reports-card {
flex: 1;
min-width: 200px;
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
text-align: center;
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
/* <-- added shadow */
transition: transform 0.2s ease, box-shadow 0.2s ease;
border-top: 1px solid #e63946;
}
.reports-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.reports-card h3 {
font-size: 14px;
margin: 0 0 10px 0;
}
.reports-card p {
margin: 5px 0;
}
.reports-card .value {
font-size: 22px;
font-weight: bold;
}
.reports-card-title {
font-size: 0.9rem;
text-transform: uppercase;
font-weight: 600;
color: #6c757d;
}
.reports-attendance {
color: #b10000;
}
.reports-tasks {
color: #007bff;
}
.reports-completion {
color: #28a745;
}
.reports-activities {
padding: 20px;
}
.reports-activities h2 {
font-size: 18px;
margin-bottom: 10px;
}
.reports-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.reports-table th,
.reports-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
.reports-table th {
background: #f0f0f0;
}
/* .footer {
background: #b10000;
color: #fff;
text-align: center;
padding: 15px;
font-size: 12px;
}
.footer a {
color: #fff;
margin: 0 8px;
text-decoration: none;
} */
/* Responsive */
@media (max-width: 600px) {
.reports-header {
flex-direction: column;
text-align: center;
}
.reports-header .reports-project-info {
text-align: center;
margin-top: 10px;
}
.reports-status-cards {
flex-direction: column;
}
}
.reports-legend {
margin-top: 10px;
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
color: #555;
}
.reports-legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.reports-legend-color {
width: 10px;
height: 10px;
border-radius: 2px;
display: inline-block;
}
.reports-legend-red {
background: #b10000;
}
.reports-legend-blue {
background: #007bff;
}
.reports-legend-green {
background: #28a745;
}
.reports-legend-gray {
background: #ccc;
}
.reports-donut {
--percentage: 65;
/* Change this per chart */
--danger: #e63946;
--primary: #007bff;
--warning: #ffc107;
--success: #198754;
/* Fill color */
--track: #e9ecef;
/* Background track */
--size: 120px;
/* Default size */
--thickness: 20px;
/* Default thickness */
width: var(--size);
height: var(--size);
border-radius: 50%;
background: conic-gradient(
var(--danger) calc(var(--percentage) * 1%),
var(--track) 0
);
position: relative;
display: flex;
align-items: center;
justify-content: center;
font-family: Arial, sans-serif;
font-weight: bold;
color: #333;
}
.reports-donut::before {
content: "";
position: absolute;
width: calc(var(--size) - var(--thickness));
height: calc(var(--size) - var(--thickness));
border-radius: 50%;
background: #fff;
/* Inner cut-out */
}
.reports-donut span {
position: absolute;
font-size: calc(var(--size) / 6);
}
/* Variants */
.reports-donut.thin {
--size: 80px;
--thickness: 12px;
}
.reports-donut.medium {
--size: 120px;
--thickness: 25px;
}
.reports-donut.large {
--size: 180px;
--thickness: 35px;
}
/* Color variants */
.reports-donut-success {
background: conic-gradient(
var(--success) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--success);
}
.reports-donut-warning {
background: conic-gradient(
var(--warning) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--warning);
}
.reports-donut-danger {
background: conic-gradient(
var(--danger) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--danger);
}
.reports-donut-primary {
background: conic-gradient(
var(--primary) calc(var(--percentage) * 1%),
var(--track) 0
);
color: var(--primary);
}

View File

@ -1,8 +1,69 @@
import React from "react"; import React, { useEffect, useState } from "react";
import { ComingSoonPage } from "../Misc/ComingSoonPage"; import { ComingSoonPage } from "../Misc/ComingSoonPage";
import ReportDPR from "../../components/reports/report-dpr";
import "./Reports.css";
import Breadcrumb from "../../components/common/Breadcrumb";
import { useProjectName } from "../../hooks/useProjects";
import DatePicker from "../../components/common/DatePicker";
import { useForm } from "react-hook-form";
const Reports = () => { const Reports = () => {
return <ComingSoonPage></ComingSoonPage>; const [selectedProject, setSelectedProject] = useState();
const { projectNames, isError, loading } = useProjectName(true);
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const { control, watch } = useForm({
defaultValues: {
startDate: yesterday.toISOString().split("T")[0],
},
});
const selelectedDate = watch("startDate");
useEffect(()=>{
if(!selectedProject && projectNames){
setSelectedProject(projectNames[0]?.id)
}
},[projectNames])
return (
<div className="container-fluid">
<Breadcrumb
data={[
{ label: "Home", link: "/dashboard" },
{ label: "Daily Progress Report", link: null },
]}
/>
<div className="card my-3 px-sm-4 px-0">
<div className="card-body py-2 px-1 mx-2">
<div className="row align-items-center mb-0">
<div className="col-12 col-md-6 ">
<div className="w-max">
<select
className="form-select form-select-sm"
onChange={(e) => setSelectedProject(e.target.value)}
>
{projectNames?.map((project) => (
<option key={project.id} value={project.id}>
{project.name}
</option>
))}
</select>
</div>
</div>
<div className="col-12 col-md-6 d-flex justify-content-end">
<DatePicker
name="startDate"
control={control}
placeholder="Select Date"
maxDate={new Date()}
/>
</div>
</div>
</div>
</div>
<ReportDPR project={selectedProject} date={selelectedDate} />
</div>
);
}; };
export default Reports; export default Reports;

View File

@ -1,7 +1,11 @@
import { api } from "../utils/axiosClient"; import { api } from "../utils/axiosClient";
const GlobalRepository = { const GlobalRepository = {
getDashboardProgressionData: ({ days = '', FromDate = '', projectId = '' }) => { getDashboardProgressionData: ({
days = "",
FromDate = "",
projectId = "",
}) => {
let params; let params;
if (projectId == null) { if (projectId == null) {
params = new URLSearchParams({ params = new URLSearchParams({
@ -18,12 +22,13 @@ const GlobalRepository = {
return api.get(`/api/Dashboard/Progression?${params.toString()}`); return api.get(`/api/Dashboard/Progression?${params.toString()}`);
}, },
getProjectCompletionStatus:()=>api.get(`/api/Dashboard/project-completion-status`), getProjectCompletionStatus: () =>
api.get(`/api/Dashboard/project-completion-status`),
getDashboardAttendanceData: (date, projectId) => { getDashboardAttendanceData: (date, projectId) => {
return api.get(
return api.get(`/api/Dashboard/project-attendance/${projectId}?date=${date}`); `/api/Dashboard/project-attendance/${projectId}?date=${date}`
);
}, },
getDashboardProjectsCardData: () => { getDashboardProjectsCardData: () => {
return api.get(`/api/Dashboard/projects`); return api.get(`/api/Dashboard/projects`);
@ -44,7 +49,7 @@ const GlobalRepository = {
}, },
getExpenseData: (projectId, startDate, endDate) => { getExpenseData: (projectId, startDate, endDate) => {
let url = `api/Dashboard/expense/type` let url = `api/Dashboard/expense/type`;
const queryParams = []; const queryParams = [];
if (projectId) { if (projectId) {
queryParams.push(`projectId=${projectId}`); queryParams.push(`projectId=${projectId}`);
@ -62,10 +67,15 @@ const GlobalRepository = {
return api.get(url); return api.get(url);
}, },
getExpenseStatus: (projectId) => api.get(`/api/Dashboard/expense/pendings${projectId ? `?projectId=${projectId}` : ""}`), getExpenseStatus: (projectId) =>
api.get(
`/api/Dashboard/expense/pendings${
projectId ? `?projectId=${projectId}` : ""
}`
),
getExpenseDataByProject: (projectId, categoryId, months) => { getExpenseDataByProject: (projectId, categoryId, months) => {
let url = `api/Dashboard/expense/monthly` let url = `api/Dashboard/expense/monthly`;
const queryParams = []; const queryParams = [];
if (projectId) { if (projectId) {
queryParams.push(`projectId=${projectId}`); queryParams.push(`projectId=${projectId}`);
@ -82,13 +92,26 @@ const GlobalRepository = {
return api.get(url); return api.get(url);
}, },
getAttendanceOverview: (projectId, days) => api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`), getAttendanceOverview: (projectId, days) =>
api.get(`/api/dashboard/attendance-overview/${projectId}?days=${days}`),
getCollectionOverview:(projectId) =>api.get(`/api/Dashboard/collection-overview`), getCollectionOverview: (projectId) =>
api.get(`/api/Dashboard/collection-overview`),
getJobsProgression: (projectId) => api.get(`/api/Dashboard/job/progression${projectId ? `?projectId=${projectId}` : ""}`), getJobsProgression: (projectId) =>
api.get(
`/api/Dashboard/job/progression${
projectId ? `?projectId=${projectId}` : ""
}`
),
getAttendanceProjectWiseOverview: (date) =>
api.get(`/api/dashboard/project/attendance-overview?date=${date}`),
}; };
export default GlobalRepository; export default GlobalRepository;
export const ReportsRepository = {
getDailyProgressByProject: (projectId,date) =>
api.get(`/api/Market/get/project/report/${projectId}?date=${date}`),
};

View File

@ -0,0 +1,64 @@
import CryptoJS from 'crypto-js';
// The key from your C# Middleware
// In a real app, prefer storing this in process.env.REACT_APP_ENCRYPTION_KEY
const KEY_BASE64 = "u4J7p9Qx2hF5vYtLz8Kq3mN1sG0bRwXyZcD6eH8jFQw=";
/**
* Decrypts the specific format sent by the C# EncryptionMiddleware.
* Format: Base64([IV (16 bytes)] + [Encrypted Data])
* * @param {string} encryptedBase64Str - The raw response text from the API
* @returns {any} - The parsed JSON object or string
*/
export const decryptResponse = (encryptedBase64Str) => {
try {
// 1. Parse the Key
const key = CryptoJS.enc.Base64.parse(KEY_BASE64);
// 2. Parse the incoming Base64 string to a WordArray
const fullWordArray = CryptoJS.enc.Base64.parse(encryptedBase64Str);
// 3. Convert to Hex to easily slice the IV (16 bytes = 32 hex chars)
// This is safer than manipulating WordArray indices directly
const fullHex = CryptoJS.enc.Hex.stringify(fullWordArray);
// 4. Extract IV (First 16 bytes / 32 hex characters)
const ivHex = fullHex.substring(0, 32);
const iv = CryptoJS.enc.Hex.parse(ivHex);
// 5. Extract Ciphertext (The rest of the string)
const cipherTextHex = fullHex.substring(32);
const cipherParams = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Hex.parse(cipherTextHex)
});
// 6. Decrypt
const decrypted = CryptoJS.AES.decrypt(
cipherParams,
key,
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}
);
// 7. Convert to UTF-8 String
const decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
if (!decryptedString) {
throw new Error("Decryption produced empty result (Wrong Key/IV?)");
}
// 8. Try to parse JSON, otherwise return plain string
try {
return JSON.parse(decryptedString);
} catch {
return decryptedString;
}
} catch (error) {
console.error("Decryption Failed:", error);
return null;
}
};

View File

@ -24,6 +24,11 @@ export const AppColorconfig = {
borderColor: "#eceef1", borderColor: "#eceef1",
}, },
}; };
export const AppColors = [
"primary","secodary","warning","info","danger"
]
export const getColorNameFromHex = (hex) => { export const getColorNameFromHex = (hex) => {
const normalizedHex = hex?.replace(/'/g, "").toLowerCase(); const normalizedHex = hex?.replace(/'/g, "").toLowerCase();
const colors = AppColorconfig.colors; const colors = AppColorconfig.colors;
@ -262,3 +267,7 @@ export function calculateTDSPercentage(baseAmount = 0, taxAmount = 0, tdsPercent
} }
export function capitalizeFirstLetter(str) {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1);
}

View File

@ -4,6 +4,7 @@ import axiosRetry from "axios-retry";
import showToast from "../services/toastService"; import showToast from "../services/toastService";
import { startSignalR, stopSignalR } from "../services/signalRService"; import { startSignalR, stopSignalR } from "../services/signalRService";
import { BASE_URL } from "./constants"; import { BASE_URL } from "./constants";
import { decryptResponse } from "../services/encryption";
const base_Url = BASE_URL; const base_Url = BASE_URL;
export const axiosClient = axios.create({ export const axiosClient = axios.create({
@ -134,7 +135,7 @@ const apiRequest = async (method, url, data = {}, config = {}) => {
params: method === "get" ? data : undefined, params: method === "get" ? data : undefined,
...config, ...config,
}); });
return response.data; return decryptResponse(response.data);
} catch (error) { } catch (error) {
throw error; throw error;
} }
@ -179,11 +180,10 @@ export const api = {
authRequired: true, authRequired: true,
}), }),
patch: (url, data = {}, customHeaders = {}) => patch: (url, data = {}, customHeaders = {}) =>
apiRequest("patch", url, data, { apiRequest("patch", url, data, {
headers: { ...customHeaders }, headers: { ...customHeaders },
authRequired: true, authRequired: true,
}), }),
}; };
// Redirect helper // Redirect helper