handle one tenant have , directly move to dashboard once logged

This commit is contained in:
Kartik Sharma 2025-09-22 00:09:34 +05:30
parent b9b3788dda
commit 4afe43d116
16 changed files with 387 additions and 313 deletions

View File

@ -1,21 +1,16 @@
import React, { useState, useEffect } from "react"; import React, { useState, useMemo } from "react";
import LineChart from "../Charts/LineChart"; import ApexChart from "../Charts/Circle";
import { useProjects } from "../../hooks/useProjects"; import { useProjects } from "../../hooks/useProjects";
import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data"; import { useDashboard_AttendanceData } from "../../hooks/useDashboard_Data";
import ApexChart from "../Charts/Circle"; import { useSelectedProject } from "../../hooks/useSelectedProject"; // your custom hook
const LOCAL_STORAGE_PROJECT_KEY = "selectedAttendanceProjectId";
const Attendance = () => { const Attendance = () => {
const { projects } = useProjects(); const { projects } = useProjects();
const today = new Date().toISOString().split("T")[0]; // Format: YYYY-MM-DD const today = new Date().toISOString().split("T")[0]; // YYYY-MM-DD
const [selectedDate, setSelectedDate] = useState(today); const [selectedDate, setSelectedDate] = useState(today);
const storedProjectId = localStorage.getItem(LOCAL_STORAGE_PROJECT_KEY);
const initialProjectId = storedProjectId || "all"; // central project selection hook
const [selectedProjectId, setSelectedProjectId] = useState(initialProjectId); const selectedProjectId = useSelectedProject()
const [displayedProjectName, setDisplayedProjectName] =
useState("Select Project");
const [activeTab, setActiveTab] = useState("Summary");
const { const {
dashboard_Attendancedata: AttendanceData, dashboard_Attendancedata: AttendanceData,
@ -23,38 +18,24 @@ const Attendance = () => {
error: isError, error: isError,
} = useDashboard_AttendanceData(selectedDate, selectedProjectId); } = useDashboard_AttendanceData(selectedDate, selectedProjectId);
useEffect(() => { // project name derived once
if (selectedProjectId === "all") { const displayedProjectName = useMemo(() => {
setDisplayedProjectName("All Projects"); if (selectedProjectId === "all") return "All Projects";
} else if (projects) { const found = projects?.find((p) => p.id === selectedProjectId);
const foundProject = projects.find((p) => p.id === selectedProjectId); return found?.name || "Select Project";
setDisplayedProjectName(
foundProject ? foundProject.name : "Select Project"
);
} else {
setDisplayedProjectName("Select Project");
}
localStorage.setItem(LOCAL_STORAGE_PROJECT_KEY, selectedProjectId);
}, [selectedProjectId, projects]); }, [selectedProjectId, projects]);
const handleProjectSelect = (projectId) => {
setSelectedProjectId(projectId);
};
const handleDateChange = (e) => {
setSelectedDate(e.target.value);
};
return ( return (
<div className="card h-100"> <div className="card h-100">
<div className="card-header mb-1 pb-0 "> {/* Header */}
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 pb-0 "> <div className="card-header mb-1 pb-0">
<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 className="mb-1">Attendance</h5>
<p className="card-subtitle">Daily Attendance Data</p> <p className="card-subtitle">Daily Attendance Data</p>
</div> </div>
{/* Project Dropdown */}
<div className="btn-group"> <div className="btn-group">
<button <button
className="btn btn-outline-primary btn-sm dropdown-toggle" className="btn btn-outline-primary btn-sm dropdown-toggle"
@ -68,7 +49,7 @@ const Attendance = () => {
<li> <li>
<button <button
className="dropdown-item" className="dropdown-item"
onClick={() => handleProjectSelect("all")} onClick={() => setSelectedProjectId("all")}
> >
All Projects All Projects
</button> </button>
@ -77,7 +58,7 @@ const Attendance = () => {
<li key={project.id}> <li key={project.id}>
<button <button
className="dropdown-item" className="dropdown-item"
onClick={() => handleProjectSelect(project.id)} onClick={() => setSelectedProjectId(project.id)}
> >
{project.name} {project.name}
</button> </button>
@ -88,52 +69,43 @@ const Attendance = () => {
</div> </div>
</div> </div>
<div className="d-flex flex-wrap justify-content-between align-items-center mb-0 mt-0 me-5 ms-5"> {/* Tabs + Date Picker */}
{/* Tabs */} <div className="d-flex flex-wrap justify-content-between align-items-center me-5 ms-5">
<div> <ul className="nav nav-tabs">
<ul className="nav nav-tabs " role="tablist"> <li className="nav-item">
<li className="nav-item"> <button
<button type="button"
type="button" className={`nav-link ${AttendanceData?.activeTab === "Summary" ? "active" : ""}`}
className={`nav-link ${ onClick={() => (AttendanceData.activeTab = "Summary")}
activeTab === "Summary" ? "active" : "" >
}`} Summary
onClick={() => setActiveTab("Summary")} </button>
data-bs-toggle="tab" </li>
> <li className="nav-item">
Summary <button
</button> type="button"
</li> className={`nav-link ${AttendanceData?.activeTab === "Details" ? "active" : ""}`}
<li className="nav-item"> onClick={() => (AttendanceData.activeTab = "Details")}
<button >
type="button" Details
className={`nav-link ${ </button>
activeTab === "Details" ? "active" : "" </li>
}`} </ul>
onClick={() => setActiveTab("Details")} <div className="ps-6 mb-3">
data-bs-toggle="tab" <input
> type="date"
Details className="form-control p-1"
</button> style={{ width: "120px" }}
</li> value={selectedDate}
</ul> onChange={(e) => setSelectedDate(e.target.value)}
</div> />
{/* ✅ Date Picker Aligned Left with Padding */}
<div className="ps-6 mb-3 mt-0">
<div style={{ width: "120px" }}>
<input
type="date"
className="form-control p-1"
// style={{ fontSize: "1rem" }}
value={selectedDate}
onChange={handleDateChange}
/>
</div>
</div> </div>
</div> </div>
{/* Body */}
<div className="card-body"> <div className="card-body">
{activeTab === "Summary" && ( {/* Summary */}
{AttendanceData?.activeTab === "Summary" && (
<div className="row justify-content-center"> <div className="row justify-content-center">
<div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4"> <div className="col-12 col-md-6 d-flex flex-column align-items-center text-center mb-4">
{isLoading ? ( {isLoading ? (
@ -143,7 +115,7 @@ const Attendance = () => {
) : ( ) : (
AttendanceData && ( AttendanceData && (
<> <>
<h5 className="fw-bold mb-0 text-center w-100"> <h5 className="fw-bold mb-0">
<i className="bx bx-task text-info"></i> Attendance <i className="bx bx-task text-info"></i> Attendance
</h5> </h5>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
@ -164,11 +136,9 @@ const Attendance = () => {
</div> </div>
)} )}
{activeTab === "Details" && ( {/* Details */}
<div {AttendanceData?.activeTab === "Details" && (
className="table-responsive" <div className="table-responsive" style={{ maxHeight: "300px" }}>
style={{ maxHeight: "300px", overflowY: "auto" }}
>
<table className="table table-hover mb-0 text-start"> <table className="table table-hover mb-0 text-start">
<thead> <thead>
<tr> <tr>
@ -178,32 +148,17 @@ const Attendance = () => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{AttendanceData?.attendanceTable && {AttendanceData?.attendanceTable?.length ? (
AttendanceData.attendanceTable.length > 0 ? ( AttendanceData.attendanceTable.map((r, i) => (
AttendanceData.attendanceTable.map((record, index) => ( <tr key={i}>
<tr key={index}> <td>{r.firstName} {r.lastName}</td>
<td> <td>{r.inTime ? new Date(r.inTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
{record.firstName} {record.lastName} <td>{r.outTime ? new Date(r.outTime).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : "-"}</td>
</td>
<td>
{new Date(record.inTime).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</td>
<td>
{new Date(record.outTime).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr>
<td colSpan="3" className="text-center"> <td colSpan="3" className="text-center">No attendance data available</td>
No attendance data available
</td>
</tr> </tr>
)} )}
</tbody> </tbody>

View File

@ -132,7 +132,7 @@ const AttendanceOverview = () => {
onClick={() => setView("table")} onClick={() => setView("table")}
title="Table View" title="Table View"
> >
<i class="bx bx-list-ul fs-5"></i> <i className="bx bx-list-ul fs-5"></i>
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,33 +1,28 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useEffect } from "react";
import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data"; import { useDashboardProjectsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import GlobalRepository from "../../repositories/GlobalRepository";
const Projects = () => { const Projects = () => {
const { projectsCardData } = useDashboardProjectsCardData(); const {
const [projectData, setProjectsData] = useState(projectsCardData); data: projectsCardData,
isLoading,
isError,
error,
refetch,
} = useDashboardProjectsCardData();
useEffect(() => { useEffect(() => {
setProjectsData(projectsCardData); // When "project" event happens, just refetch
}, [projectsCardData]); const handler = () => {
refetch();
};
const handler = useCallback(
async (msg) => {
try {
const response =
await GlobalRepository.getDashboardProjectsCardData();
setProjectsData(response.data);
} catch (err) {
console.error(err);
}
},
[GlobalRepository]
);
useEffect(() => {
eventBus.on("project", handler); eventBus.on("project", handler);
return () => eventBus.off("project", handler); return () => eventBus.off("project", handler);
}, [handler]); }, [refetch]);
const totalProjects = projectsCardData?.totalProjects ?? 0;
const ongoingProjects = projectsCardData?.ongoingProjects ?? 0;
return ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -37,20 +32,29 @@ const Projects = () => {
Projects Projects
</h5> </h5>
</div> </div>
<div className="d-flex justify-content-around align-items-start mt-n2">
<div> {isLoading ? (
<h4 className="mb-0 fw-bold"> <div className="d-flex justify-content-center align-items-center flex-grow-1">
{projectData.totalProjects?.toLocaleString()} <div className="spinner-border text-primary" role="status">
</h4> <span className="visually-hidden">Loading...</span>
<small className="text-muted">Total</small> </div>
</div> </div>
<div> ) : isError ? (
<h4 className="mb-0 fw-bold"> <div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{projectData.ongoingProjects?.toLocaleString()} {error?.message || "Error loading data"}
</h4>
<small className="text-muted">Ongoing</small>
</div> </div>
</div> ) : (
<div className="d-flex justify-content-around align-items-start mt-n2">
<div>
<h4 className="mb-0 fw-bold">{totalProjects.toLocaleString()}</h4>
<small className="text-muted">Total</small>
</div>
<div>
<h4 className="mb-0 fw-bold">{ongoingProjects.toLocaleString()}</h4>
<small className="text-muted">Ongoing</small>
</div>
</div>
)}
</div> </div>
); );
}; };

View File

@ -1,10 +1,16 @@
import React from "react"; import React from "react";
import { useSelector } from "react-redux"; import { useSelectedProject } from "../../slices/apiDataManager";
import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data"; import { useDashboardTasksCardData } from "../../hooks/useDashboard_Data";
const TasksCard = () => { const TasksCard = () => {
const projectId = useSelector((store) => store.localVariables?.projectId); const projectId = useSelectedProject();
const { tasksCardData, loading, error } = useDashboardTasksCardData(projectId);
const {
data: tasksCardData,
isLoading,
isError,
error,
} = useDashboardTasksCardData(projectId);
return ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <div className="card p-3 h-100 text-center d-flex justify-content-between">
@ -14,28 +20,30 @@ const TasksCard = () => {
</h5> </h5>
</div> </div>
{loading ? ( {isLoading ? (
// Loader will be displayed when loading is true // Loader while fetching
<div className="d-flex justify-content-center align-items-center flex-grow-1"> <div className="d-flex justify-content-center align-items-center flex-grow-1">
<div className="spinner-border text-primary" role="status"> <div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
) : error ? ( ) : isError ? (
// Error message if there's an error // Show error
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div> <div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
{error?.message || "Error loading data"}
</div>
) : ( ) : (
// Actual data when loaded successfully // Show data
<div className="d-flex justify-content-around align-items-start mt-n2"> <div className="d-flex justify-content-around align-items-start mt-n2">
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{tasksCardData?.totalTasks?.toLocaleString()} {tasksCardData?.totalTasks?.toLocaleString() ?? 0}
</h4> </h4>
<small className="text-muted">Total</small> <small className="text-muted">Total</small>
</div> </div>
<div> <div>
<h4 className="mb-0 fw-bold"> <h4 className="mb-0 fw-bold">
{tasksCardData?.completedTasks?.toLocaleString()} {tasksCardData?.completedTasks?.toLocaleString() ?? 0}
</h4> </h4>
<small className="text-muted">Completed</small> <small className="text-muted">Completed</small>
</div> </div>
@ -45,4 +53,4 @@ const TasksCard = () => {
); );
}; };
export default TasksCard; export default TasksCard;

View File

@ -1,33 +1,45 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect } from "react";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data"; import { useDashboardTeamsCardData } from "../../hooks/useDashboard_Data";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";
import { useQueryClient } from "@tanstack/react-query";
import { useSelectedProject } from "../../slices/apiDataManager";
const Teams = () => { const Teams = () => {
const projectId = useSelector((store) => store.localVariables?.projectId); const queryClient = useQueryClient();
const { teamsCardData, loading, error } = useDashboardTeamsCardData(projectId); const projectId = useSelectedProject()
const [totalEmployees, setTotalEmployee] = useState(0); const {
const [inToday, setInToday] = useState(0); data: teamsCardData,
isLoading,
// Update state when API data arrives isError,
useEffect(() => { error,
setTotalEmployee(teamsCardData?.totalEmployees || 0); } = useDashboardTeamsCardData(projectId);
setInToday(teamsCardData?.inToday || 0);
}, [teamsCardData]);
// Handle real-time updates via eventBus // Handle real-time updates via eventBus
const handler = useCallback((msg) => { const handler = useCallback(
if (msg.activity === 1) { (msg) => {
setInToday((prev) => prev + 1); if (msg.activity === 1) {
} queryClient.setQueryData(["dashboardTeams", projectId], (old) => {
}, []); if (!old) return old;
return {
...old,
inToday: (old.inToday || 0) + 1,
};
});
}
},
[queryClient, projectId]
);
useEffect(() => { useEffect(() => {
eventBus.on("attendance", handler); eventBus.on("attendance", handler);
return () => eventBus.off("attendance", handler); return () => eventBus.off("attendance", handler);
}, [handler]); }, [handler]);
const inToday = teamsCardData?.inToday ?? 0;
const totalEmployees = teamsCardData?.totalEmployees ?? 0;
return ( return (
<div className="card p-3 h-100 text-center d-flex justify-content-between"> <div className="card p-3 h-100 text-center d-flex justify-content-between">
<div className="d-flex justify-content-start align-items-center mb-3"> <div className="d-flex justify-content-start align-items-center mb-3">
@ -36,18 +48,17 @@ const Teams = () => {
</h5> </h5>
</div> </div>
{loading ? ( {isLoading ? (
// Blue spinner loader
<div className="d-flex justify-content-center align-items-center flex-grow-1"> <div className="d-flex justify-content-center align-items-center flex-grow-1">
<div className="spinner-border text-primary" role="status"> <div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span> <span className="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
) : error ? ( ) : isError ? (
// Error message if data fetching fails <div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">
<div className="text-danger flex-grow-1 d-flex justify-content-center align-items-center">{error}</div> {error?.message || "Error loading data"}
</div>
) : ( ) : (
// Display data once loaded
<div className="d-flex justify-content-around align-items-start mt-n2"> <div className="d-flex justify-content-around align-items-start mt-n2">
<div> <div>
<h4 className="mb-0 fw-bold">{totalEmployees.toLocaleString()}</h4> <h4 className="mb-0 fw-bold">{totalEmployees.toLocaleString()}</h4>
@ -63,4 +74,4 @@ const Teams = () => {
); );
}; };
export default Teams; export default Teams;

View File

@ -1,5 +1,4 @@
import React from "react"; import React from "react";
import { hasUserPermission } from "../../utils/authUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { import {
DIRECTORY_ADMIN, DIRECTORY_ADMIN,

View File

@ -5,12 +5,12 @@ import GlobalModel from "../common/GlobalModel";
import { useTenantContext } from "../../pages/Tenant/TenantPage"; import { useTenantContext } from "../../pages/Tenant/TenantPage";
import { useTenantDetailsContext } from "../../pages/Tenant/TenantDetails"; import { useTenantDetailsContext } from "../../pages/Tenant/TenantDetails";
import IconButton from "../common/IconButton"; import IconButton from "../common/IconButton";
import { hasUserPermission } from "../../utils/authUtils";
import { MANAGE_TENANTS } from "../../utils/constants"; import { MANAGE_TENANTS } from "../../utils/constants";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const Profile = ({ data }) => { const Profile = ({ data }) => {
const {setEditTenant} = useTenantDetailsContext() const {setEditTenant} = useTenantDetailsContext()
const canUpdateTenant = hasUserPermission(MANAGE_TENANTS) const canUpdateTenant = useHasUserPermission(MANAGE_TENANTS)
return ( return (
<> <>
<div className="container-fuid"> <div className="container-fuid">

View File

@ -28,13 +28,18 @@ export const useSelectTenant = (onSuccessCallBack) => {
const res = await AuthRepository.selectTenant(tenantId); const res = await AuthRepository.selectTenant(tenantId);
return res.data; return res.data;
}, },
onSuccess: (data) => { onSuccess: (data) => {
localStorage.setItem("ltkn", data.token); localStorage.setItem("jwtToken", data.token);
localStorage.setItem("rtkn", data.refreshToken); localStorage.setItem("refreshToken", data.refreshToken);
if (onSuccessCallBack) onSuccessCallBack(); if (onSuccessCallBack) onSuccessCallBack();
}, },
onError: (error) => { onError: (error) => {
showToast(error.message || "Error while creating project", "error"); showToast(error.message || "Error while creating project", "error");
localStorage.removeItem("jwtToken");
localStorage.removeItem("refreshToken")
localStorage.removeItem("ctnt")
}, },
}); });
}; };

View File

@ -1,7 +1,8 @@
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";
// 🔹 Dashboard Progression Data Hook
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([]);
const [isLineChartLoading, setLoading] = useState(false); const [isLineChartLoading, setLoading] = useState(false);
@ -38,120 +39,120 @@ export const useDashboard_Data = ({ days, FromDate, projectId }) => {
}; };
export const useDashboard_AttendanceData = (date, projectId) => { // export const useDashboard_AttendanceData = (date, projectId) => {
const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]); // const [dashboard_Attendancedata, setDashboard_AttendanceData] = useState([]);
const [isLineChartLoading, setLoading] = useState(false); // const [isLineChartLoading, setLoading] = useState(false);
const [error, setError] = useState(""); // const [error, setError] = useState("");
useEffect(() => { // useEffect(() => {
const fetchData = async () => { // const fetchData = async () => {
setLoading(true); // setLoading(true);
setError(""); // setError("");
try { // try {
const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param // const response = await GlobalRepository.getDashboardAttendanceData(date, projectId); // date in 2nd param
setDashboard_AttendanceData(response.data); // setDashboard_AttendanceData(response.data);
} catch (err) { // } catch (err) {
setError("Failed to fetch dashboard data."); // setError("Failed to fetch dashboard data.");
console.error(err); // console.error(err);
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
if (date && projectId !== null) { // if (date && projectId !== null) {
fetchData(); // fetchData();
} // }
}, [date, projectId]); // }, [date, projectId]);
return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error }; // return { dashboard_Attendancedata, isLineChartLoading: isLineChartLoading, error };
}; // };
// 🔹 Dashboard Projects Card Data Hook // 🔹 Dashboard Projects Card Data Hook
export const useDashboardProjectsCardData = () => { // export const useDashboardProjectsCardData = () => {
const [projectsCardData, setProjectsData] = useState([]); // const [projectsCardData, setProjectsData] = useState([]);
const [loading, setLoading] = useState(false); // const [loading, setLoading] = useState(false);
const [error, setError] = useState(""); // const [error, setError] = useState("");
useEffect(() => { // useEffect(() => {
const fetchProjectsData = async () => { // const fetchProjectsData = async () => {
setLoading(true); // setLoading(true);
setError(""); // setError("");
try { // try {
const response = await GlobalRepository.getDashboardProjectsCardData(); // const response = await GlobalRepository.getDashboardProjectsCardData();
setProjectsData(response.data); // setProjectsData(response.data);
} catch (err) { // } catch (err) {
setError("Failed to fetch projects card data."); // setError("Failed to fetch projects card data.");
console.error(err); // console.error(err);
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
fetchProjectsData(); // fetchProjectsData();
}, []); // }, []);
return { projectsCardData, loading, error }; // return { projectsCardData, loading, error };
}; // };
// 🔹 Dashboard Teams Card Data Hook // 🔹 Dashboard Teams Card Data Hook
export const useDashboardTeamsCardData = (projectId) => { // export const useDashboardTeamsCardData = (projectId) => {
const [teamsCardData, setTeamsData] = useState({}); // const [teamsCardData, setTeamsData] = useState({});
const [loading, setLoading] = useState(false); // const [loading, setLoading] = useState(false);
const [error, setError] = useState(""); // const [error, setError] = useState("");
useEffect(() => { // useEffect(() => {
const fetchTeamsData = async () => { // const fetchTeamsData = async () => {
setLoading(true); // setLoading(true);
setError(""); // setError("");
try { // try {
const response = await GlobalRepository.getDashboardTeamsCardData(projectId); // const response = await GlobalRepository.getDashboardTeamsCardData(projectId);
setTeamsData(response.data || {}); // setTeamsData(response.data || {});
} catch (err) { // } catch (err) {
setError("Failed to fetch teams card data."); // setError("Failed to fetch teams card data.");
console.error("Error fetching teams card data:", err); // console.error("Error fetching teams card data:", err);
setTeamsData({}); // setTeamsData({});
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
fetchTeamsData(); // fetchTeamsData();
}, [projectId]); // }, [projectId]);
return { teamsCardData, loading, error }; // return { teamsCardData, loading, error };
}; // };
export const useDashboardTasksCardData = (projectId) => { // export const useDashboardTasksCardData = (projectId) => {
const [tasksCardData, setTasksData] = useState({}); // const [tasksCardData, setTasksData] = useState({});
const [loading, setLoading] = useState(false); // const [loading, setLoading] = useState(false);
const [error, setError] = useState(""); // const [error, setError] = useState("");
useEffect(() => { // useEffect(() => {
const fetchTasksData = async () => { // const fetchTasksData = async () => {
setLoading(true); // setLoading(true);
setError(""); // setError("");
try { // try {
const response = await GlobalRepository.getDashboardTasksCardData(projectId); // const response = await GlobalRepository.getDashboardTasksCardData(projectId);
setTasksData(response.data); // setTasksData(response.data);
} catch (err) { // } catch (err) {
setError("Failed to fetch tasks card data."); // setError("Failed to fetch tasks card data.");
console.error(err); // console.error(err);
setTasksData({}); // setTasksData({});
} finally { // } finally {
setLoading(false); // setLoading(false);
} // }
}; // };
fetchTasksData(); // fetchTasksData();
}, [projectId]); // }, [projectId]);
return { tasksCardData, loading, error }; // return { tasksCardData, loading, error };
}; // };
export const useAttendanceOverviewData = (projectId, days) => { export const useAttendanceOverviewData = (projectId, days) => {
@ -180,3 +181,75 @@ export const useAttendanceOverviewData = (projectId, days) => {
return { attendanceOverviewData, loading, error }; return { attendanceOverviewData, loading, error };
}; };
// -------------------Query----------------------------
// export const useDashboard_Data = (days, FromDate, projectId)=>{
// return useQuery({
// queryKey:["dashboardProjectProgress"],
// queryFn:async()=> {
// const payload = {
// days,
// FromDate: FromDate || '',
// projectId: projectId || null,
// };
// const resp = await GlobalRepository.getDashboardProgressionData(payload);
// return resp.data;
// }
// })
// }
export const useDashboard_AttendanceData = (date,projectId)=>{
return useQuery({
queryKey:["dashboardAttendances",date,projectId],
queryFn:async()=> {
const resp = await await GlobalRepository.getDashboardAttendanceData(date, projectId)
return resp.data;
}
})
}
export const useDashboardTeamsCardData =(projectId)=>{
return useQuery({
queryKey:["dashboardTeams",projectId],
queryFn:async()=> {
const resp = await GlobalRepository.getDashboardTeamsCardData(projectId)
return resp.data;
}
})
}
export const useDashboardTasksCardData = (projectId) => {
return useQuery({
queryKey:["dashboardTasks",projectId],
queryFn:async()=> {
const resp = await GlobalRepository.getDashboardTasksCardData(projectId)
return resp.data;
}
})
}
// export const useAttendanceOverviewData = (projectId, days) => {
// return useQuery({
// queryKey:["dashboardAttendanceOverView",projectId],
// queryFn:async()=> {
// const resp = await GlobalRepository.getAttendanceOverview(projectId, days);
// return resp.data;
// }
// })
// }
export const useDashboardProjectsCardData = () => {
return useQuery({
queryKey:["dashboardProjects"],
queryFn:async()=> {
const resp = await GlobalRepository.getDashboardProjectsCardData();
return resp.data;
}
})
}

View File

@ -13,7 +13,6 @@ import Regularization from "../../components/Activities/Regularization";
import { useAttendance } from "../../hooks/useAttendance"; import { useAttendance } from "../../hooks/useAttendance";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import { hasUserPermission } from "../../utils/authUtils";
import { useHasUserPermission } from "../../hooks/useHasUserPermission"; import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import { REGULARIZE_ATTENDANCE } from "../../utils/constants"; import { REGULARIZE_ATTENDANCE } from "../../utils/constants";
import eventBus from "../../services/eventBus"; import eventBus from "../../services/eventBus";

View File

@ -1,16 +1,16 @@
import React, { useEffect, useMemo } from "react"; import React, { useEffect, useMemo } from "react";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import TenantDetails from "./TenantDetails"; import TenantDetails from "./TenantDetails";
import { hasUserPermission } from "../../utils/authUtils";
import { VIEW_TENANTS } from "../../utils/constants"; import { VIEW_TENANTS } from "../../utils/constants";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Loader from "../../components/common/Loader"; import Loader from "../../components/common/Loader";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const SelfTenantDetails = () => { const SelfTenantDetails = () => {
const { profile, loading } = useProfile(); const { profile, loading } = useProfile();
const tenantId = profile?.employeeInfo?.tenantId; const tenantId = profile?.employeeInfo?.tenantId;
const navigate = useNavigate(); const navigate = useNavigate();
const isSelfTenantView = hasUserPermission(VIEW_TENANTS); const isSelfTenantView = useHasUserPermission(VIEW_TENANTS);
useEffect(() => { useEffect(() => {
if (!isSelfTenantView) { if (!isSelfTenantView) {

View File

@ -20,7 +20,6 @@ import TenantFilterPanel from "../../components/Tenant/TenantFilterPanel";
import { useDebounce } from "../../utils/appUtils"; import { useDebounce } from "../../utils/appUtils";
import { useFab } from "../../Context/FabContext"; import { useFab } from "../../Context/FabContext";
import { setCurrentTenant } from "../../slices/globalVariablesSlice"; import { setCurrentTenant } from "../../slices/globalVariablesSlice";
import { hasUserPermission } from "../../utils/authUtils";
// ------ Schema ------- // ------ Schema -------
import { import {
@ -35,6 +34,7 @@ import {
VIEW_TENANTS, VIEW_TENANTS,
} from "../../utils/constants"; } from "../../utils/constants";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
// ---------- Context ---------- // ---------- Context ----------
export const TenantContext = createContext(); export const TenantContext = createContext();
@ -63,9 +63,9 @@ const TenantPage = () => {
const debouncedSearch = useDebounce(searchText, 500); const debouncedSearch = useDebounce(searchText, 500);
const { setOffcanvasContent, setShowTrigger } = useFab(); const { setOffcanvasContent, setShowTrigger } = useFab();
const isSuperTenant = hasUserPermission(SUPPER_TENANT); const isSuperTenant = useHasUserPermission(SUPPER_TENANT);
const canManageTenants = hasUserPermission(MANAGE_TENANTS); const canManageTenants = useHasUserPermission(MANAGE_TENANTS);
const isSelfTenant = hasUserPermission(VIEW_TENANTS); const isSelfTenant = useHasUserPermission(VIEW_TENANTS);
const methods = useForm({ const methods = useForm({
resolver: zodResolver(filterSchema), resolver: zodResolver(filterSchema),

View File

@ -4,6 +4,7 @@ import { useAuthModal, useSelectTenant, useTenants } from "../../hooks/useAuth";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import AuthRepository from "../../repositories/AuthRepository"; import AuthRepository from "../../repositories/AuthRepository";
import Loader from "../../components/common/Loader";
const SwitchTenant = () => { const SwitchTenant = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@ -27,6 +28,8 @@ const SwitchTenant = () => {
localStorage.setItem("ctnt", tenantId); localStorage.setItem("ctnt", tenantId);
chooseTenant(tenantId); chooseTenant(tenantId);
}; };
const contentBody = ( const contentBody = (
<div className="container text-black"> <div className="container text-black">
<p className=" fs-5">Switch Workplace</p> <p className=" fs-5">Switch Workplace</p>
@ -82,7 +85,7 @@ const SwitchTenant = () => {
<button <button
className="btn btn-primary btn-sm mt-2 align-self-start" className="btn btn-primary btn-sm mt-2 align-self-start"
onClick={() => handleTenantselection(tenant?.id)} onClick={() => handleTenantselection(tenant?.id)}
disabled={isPending && pendingTenant === tenant.id} disabled={isPending && pendingTenant === tenant.id || currentTenant === tenant.id }
> >
{isPending && pendingTenant === tenant.id {isPending && pendingTenant === tenant.id
? "Please Wait.." ? "Please Wait.."
@ -95,7 +98,7 @@ const SwitchTenant = () => {
</div> </div>
</div> </div>
); );
return <Modal isOpen={isOpen} onClose={onClose} body={contentBody} />; return <Modal isOpen={isOpen} onClose={onClose} body={isLoading ? <Loader/>:contentBody} />;
}; };
export default SwitchTenant; export default SwitchTenant;

View File

@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
import { useTenants, useSelectTenant } from "../../hooks/useAuth.jsx"; import { useTenants, useSelectTenant } from "../../hooks/useAuth.jsx";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import Dashboard from "../../components/Dashboard/Dashboard.jsx"; import Dashboard from "../../components/Dashboard/Dashboard.jsx";
import Loader from "../../components/common/Loader.jsx";
const TenantSelectionPage = () => { const TenantSelectionPage = () => {
const [pendingTenant, setPendingTenant] = useState(null); const [pendingTenant, setPendingTenant] = useState(null);
@ -19,26 +20,48 @@ const TenantSelectionPage = () => {
useEffect(() => { useEffect(() => {
if (localStorage.getItem("ctnt")) { if (localStorage.getItem("ctnt")) {
return navigate("/dashboard"); navigate("/dashboard");
} }
}, []); }, [navigate]);
useEffect(() => {
if (!isLoading && data?.data?.length === 1) {
const tenant = data.data[0];
handleTenantselection(tenant.id);
}
}, [isLoading, data]);
if (isLoading) return <Loader />;
if (isLoading) { if (isLoading) {
return <Loader />;
}
if (!data?.data?.length) {
return ( return (
<div className="container-fluid py-5 text-center"> <div className="text-center py-5">
<div className="spinner-border text-primary" role="status"> <p>No tenant assigned to your account.</p>
<span className="visually-hidden">Loading tenants...</span>
</div>
</div> </div>
); );
} }
return ( return (
<div className="container-fluid py-4"> <div className="container-fluid py-4">
{/* Header */} {/* Logo */}
<div className="text-center">
<img
src="/img/brand/marco.png"
alt="marco-logo"
className="app-brand-logo-login img-fluid"
style={{ maxHeight: "80px" }}
/>
</div>
{/* Heading */}
<div className="text-center mb-4"> <div className="text-center mb-4">
<p className="fs-4 mb-1">Welcome</p> <p className="fs-4 fw-bold mb-1">Welcome</p>
<p className="fs-5"> <p className="fs-6 fs-md-5">
Please select which dashboard you want to explore!!! Please select which dashboard you want to explore!!!
</p> </p>
</div> </div>
@ -47,40 +70,45 @@ const TenantSelectionPage = () => {
<div className="row justify-content-center g-4 m-auto"> <div className="row justify-content-center g-4 m-auto">
{data?.data.map((tenant) => ( {data?.data.map((tenant) => (
<div key={tenant.id} className="col-12 col-md-10 col-lg-8"> <div key={tenant.id} className="col-12 col-md-10 col-lg-8">
<div className="d-flex flex-column flex-md-row gap-5 align-items-center align-items-md-start p-3 border rounded shadow-sm bg-white h-100"> <div className="d-flex flex-column flex-md-row gap-4 align-items-center align-items-md-start p-3 border rounded shadow-sm bg-white h-100">
{/* Image */} {/* Image */}
<div className="flex-shrink-0 text-center"> <div className="flex-shrink-0 text-center">
<img <img
src={tenant?.logoImage || "/assets/img/SP-Placeholdeer.svg"} src={tenant?.logoImage || "/assets/img/SP-Placeholdeer.svg"}
alt={tenant.name} alt={tenant.name}
className="img-fluid" className="img-fluid rounded"
style={{ style={{
maxWidth: "140px", maxWidth: "140px",
height: "auto",
aspectRatio: "3 / 2", aspectRatio: "3 / 2",
objectFit: "contain",
}} }}
/> />
</div> </div>
{/* Content */} {/* Content */}
<div className="d-flex flex-column text-start gap-2"> <div className="d-flex flex-column text-start gap-2 w-100">
<p className="fs-5 text-muted fw-semibold mb-1"> {/* Title */}
<p className="fs-5 fs-md-4 text-dark fw-semibold mb-1">
{tenant?.name} {tenant?.name}
</p> </p>
{/* Industry */}
<div className="d-flex flex-wrap gap-2 align-items-center"> <div className="d-flex flex-wrap gap-2 align-items-center">
<p className="fw-semibold m-0">Industry:</p> <p className="fw-semibold m-0">Industry:</p>
<p className="m-0"> <p className="m-0 text-muted">
{tenant?.industry?.name || "Not Available"} {tenant?.industry?.name || "Not Available"}
</p> </p>
</div> </div>
{/* Description */}
{tenant?.description && ( {tenant?.description && (
<p className="text-start text-wrap m-0"> <p className="text-start text-wrap text-muted small m-0">
{tenant?.description} {tenant?.description}
</p> </p>
)} )}
{/* Button */}
<button <button
className="btn btn-primary btn-sm mt-2 align-self-start" className="btn btn-primary btn-sm mt-2 align-self-start"
onClick={() => handleTenantselection(tenant?.id)} onClick={() => handleTenantselection(tenant?.id)}
@ -96,6 +124,7 @@ const TenantSelectionPage = () => {
))} ))}
</div> </div>
</div> </div>
); );
}; };

View File

@ -11,7 +11,7 @@ import {
} from "../../hooks/useEmployees"; } from "../../hooks/useEmployees";
import { useProjectName, useProjects } from "../../hooks/useProjects"; import { useProjectName, useProjects } from "../../hooks/useProjects";
import { useProfile } from "../../hooks/useProfile"; import { useProfile } from "../../hooks/useProfile";
import { hasUserPermission } from "../../utils/authUtils";
import { import {
ITEMS_PER_PAGE, ITEMS_PER_PAGE,
MANAGE_EMPLOYEES, MANAGE_EMPLOYEES,
@ -19,7 +19,6 @@ import {
VIEW_TEAM_MEMBERS, VIEW_TEAM_MEMBERS,
} from "../../utils/constants"; } from "../../utils/constants";
import { clearCacheKey } from "../../slices/apiDataManager"; import { clearCacheKey } from "../../slices/apiDataManager";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp import SuspendEmp from "../../components/Employee/SuspendEmp"; // Keep if you use SuspendEmp
import { import {
exportToCSV, exportToCSV,
@ -36,6 +35,7 @@ import { newlineChars } from "pdf-lib";
import GlobalModel from "../../components/common/GlobalModel"; import GlobalModel from "../../components/common/GlobalModel";
import usePagination from "../../hooks/usePagination"; import usePagination from "../../hooks/usePagination";
import { setProjectId } from "../../slices/localVariablesSlice"; import { setProjectId } from "../../slices/localVariablesSlice";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const EmployeeList = () => { const EmployeeList = () => {
const selectedProjectId = useSelector( const selectedProjectId = useSelector(

View File

@ -1,12 +0,0 @@
import { useProfile } from "../hooks/useProfile";
export const hasUserPermission = (permission) => {
const { profile } = useProfile();
if (profile) {
if (!permission || typeof permission !== "string") {
return false;
}
return profile?.featurePermissions.includes(permission);
}
return false;
};