fixed dashbord card size

This commit is contained in:
pramod.mahajan 2025-10-04 15:47:07 +05:30
parent b53e6284b9
commit f0f579beae
8 changed files with 243 additions and 165 deletions

View File

@ -25,3 +25,72 @@
.text-md-b {
font-weight: normal;
}
.text-xxs { font-size: 0.55rem; } /* 8px */
.text-xs { font-size: 0.75rem; } /* 12px */
.text-sm { font-size: 0.875rem; } /* 14px */
.text-base { font-size: 1rem; } /* 16px */
.text-lg { font-size: 1.125rem; } /* 18px */
.text-xl { font-size: 1.25rem; } /* 20px */
.text-2xl { font-size: 1.5rem; } /* 24px */
.text-3xl { font-size: 1.875rem; } /* 30px */
.text-4xl { font-size: 2.25rem; } /* 36px */
.text-5xl { font-size: 3rem; } /* 48px */
.text-6xl { font-size: 3.75rem; } /* 60px */
.text-7xl { font-size: 4.5rem; } /* 72px */
.text-8xl { font-size: 6rem; } /* 96px */
.text-9xl { font-size: 8rem; } /* 128px */
/* */
.w-0 { width: 0px; }
.w-px { width: 1px; }
.w-1 { width: 0.25rem; } /* 4px */
.w-2 { width: 0.5rem; } /* 8px */
.w-3 { width: 0.75rem; } /* 12px */
.w-4 { width: 1rem; } /* 16px */
.w-5 { width: 1.25rem; } /* 20px */
.w-6 { width: 1.5rem; } /* 24px */
.w-8 { width: 2rem; } /* 32px */
.w-10 { width: 2.5rem; } /* 40px */
.w-12 { width: 3rem; } /* 48px */
.w-16 { width: 4rem; } /* 64px */
.w-20 { width: 5rem; } /* 80px */
.w-24 { width: 6rem; } /* 96px */
.w-32 { width: 8rem; } /* 128px */
.w-40 { width: 10rem; } /* 160px */
.w-48 { width: 12rem; } /* 192px */
.w-56 { width: 14rem; } /* 224px */
.w-64 { width: 16rem; } /* 256px */
.w-auto { width: auto; }
.w-full { width: 100%; }
.w-screen{ width: 100vw; }
.w-min { width: min-content; }
.w-max { width: max-content; }
.h-0 { height: 0px; }
.h-px { height: 1px; }
.h-1 { height: 0.25rem; } /* 4px */
.h-2 { height: 0.5rem; } /* 8px */
.h-3 { height: 0.75rem; } /* 12px */
.h-4 { height: 1rem; } /* 16px */
.h-5 { height: 1.25rem; } /* 20px */
.h-6 { height: 1.5rem; } /* 24px */
.h-8 { height: 2rem; } /* 32px */
.h-10 { height: 2.5rem; } /* 40px */
.h-12 { height: 3rem; } /* 48px */
.h-16 { height: 4rem; } /* 64px */
.h-20 { height: 5rem; } /* 80px */
.h-24 { height: 6rem; } /* 96px */
.h-32 { height: 8rem; } /* 128px */
.h-40 { height: 10rem; } /* 160px */
.h-48 { height: 12rem; } /* 192px */
.h-56 { height: 14rem; } /* 224px */
.h-64 { height: 16rem; } /* 256px */
.h-auto { height: auto; }
.h-full { height: 100%; }
.h-screen{ height: 100vh; }
.h-min { height: min-content; }
.h-max { height: max-content; }

View File

@ -31,7 +31,7 @@ const selectedProjectId = useSelectedProject()
<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">
<h5 className="mb-1">Attendance</h5>
<h5 className="mb-1 card-title">Attendance</h5>
<p className="card-subtitle">Daily Attendance Data</p>
</div>

View File

@ -65,7 +65,7 @@ const AttendanceOverview = () => {
};
return (
<div className="bg-white p-4 rounded shadow d-flex flex-column position-relative">
<div className="bg-white p-4 rounded shadow d-flex flex-column position-relative " >
{/* Optional subtle loading overlay */}
{isLoading && (
<div className="position-absolute w-100 h-100 d-flex align-items-center justify-content-center bg-white bg-opacity-50 z-index-1">
@ -76,7 +76,7 @@ const AttendanceOverview = () => {
{/* Header */}
<div className="d-flex justify-content-between align-items-center mb-3">
<div className="card-title mb-0 text-start">
<h5 className="mb-1 fw-bold">Attendance Overview</h5>
<h5 className="mb-1 card-title">Attendance Overview</h5>
<p className="card-subtitle">Role-wise present count</p>
</div>
<div className="d-flex gap-2">
@ -98,7 +98,7 @@ const AttendanceOverview = () => {
<div className="flex-grow-1 d-flex align-items-center justify-content-center">
{view === "chart" ? (
<div className="w-100">
<ReactApexChart options={chartOptions} series={chartSeries} type="bar" height={400} />
<ReactApexChart options={chartOptions} series={chartSeries} type="bar" height={300} />
</div>
) : (
<div className="table-responsive w-100" style={{ maxHeight: "350px", overflowY: "auto" }}>

View File

@ -30,7 +30,7 @@ const Dashboard = () => {
const isAllProjectsSelected = projectId === null;
return (
<div className="container-xxl flex-grow-1 container-p-y">
<div className="container-fluid py-5">
<div className="row mb-6 g-6">
<div className="col-12 col-xl-8">
<div className="card h-100">
@ -39,7 +39,7 @@ const Dashboard = () => {
</div>
<div className="col-12 col-xl-4 col-md-6">
<div className="card h-100">
<div className="card ">
<ExpenseStatus />
</div>
</div>

View File

@ -71,9 +71,9 @@ const { labels, series, total } = useMemo(() => {
return (
<>
<div className="card-header d-flex justify-content-between mt-2 align-items-center ">
<div className="card-header d-flex justify-content-between align-items-center ">
<div>
<h5 className="mb-1 fw-bold text-start">Expense Breakdown</h5>
<h5 className="mb-1 card-title text-start">Expense Breakdown</h5>
<p className="card-subtitle">Category Wise Expense Breakdown</p>
</div>
@ -109,7 +109,7 @@ const { labels, series, total } = useMemo(() => {
</div>
)}
<div className="d-flex justify-content-center mb-8">
<div className="d-flex justify-content-center mb-3">
<Chart
options={donutOptions}
series={report.map((item) => item.totalApprovedAmount || 0)}

View File

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

View File

@ -3,9 +3,10 @@ import { useExpense } from "../../hooks/useExpense";
import { useExpenseStatus } from "../../hooks/useDashboard_Data";
import { useSelectedProject } from "../../slices/apiDataManager";
import { useProjectName } from "../../hooks/useProjects";
import { formatCurrency } from "../../utils/appUtils";
import { EXPENSE_STATUS } from "../../utils/constants";
import { countDigit, formatCurrency } from "../../utils/appUtils";
import { EXPENSE_MANAGE, EXPENSE_STATUS } from "../../utils/constants";
import { useNavigate } from "react-router-dom";
import { useHasUserPermission } from "../../hooks/useHasUserPermission";
const ExpenseStatus = () => {
const [projectName, setProjectName] = useState("All Project");
@ -13,6 +14,7 @@ const ExpenseStatus = () => {
const { projectNames, loading } = useProjectName();
const { data, isPending, error } = useExpenseStatus(selectedProject);
const navigate = useNavigate();
const isManageExpense = useHasUserPermission(EXPENSE_MANAGE)
useEffect(() => {
if (selectedProject && projectNames?.length) {
@ -32,7 +34,7 @@ const ExpenseStatus = () => {
};
return (
<>
<div className="card-header d-flex justify-content-between text-start">
<div className="card-header d-flex justify-content-between text-start ">
<div className="m-0">
<h5 className="card-title mb-1">Expense - By Status</h5>
<p className="card-subtitle m-0 ">{projectName}</p>
@ -41,15 +43,15 @@ const ExpenseStatus = () => {
<div className="card-body ">
<div className=" py-0 text-start mb-2">
<div className="d-flex justify-content-between align-items-center">
{isManageExpense && ( <div className="d-flex justify-content-between align-items-center">
<div className="d-block">
<span className="fs-5 d-block">Project Spendings:</span>{" "}
<small className="d-block text-gary-80">{`(All Processed Payments)`}</small>
<span className={`fs-semibold d-block ${countDigit(data?.totalAmount) > 3 ? "text-base":"text-lg" }`}>Project Spendings:</span>{" "}
<small className="d-block text-xxs text-gary-80">{`(All Processed Payments)`}</small>
</div>
<span className="text-end text-royalblue text-md">
<span className={`text-end text-royalblue ${countDigit(data?.totalAmount) > 3 ? "text-":"text-3xl" } text-md`}>
{formatCurrency(data?.totalAmount)}
</span>
</div>
</div>)}
</div>
<div className="report-list text-start">
{[
@ -70,7 +72,7 @@ const ExpenseStatus = () => {
status: EXPENSE_STATUS.approve_pending,
},
{
title: "Pending Reviewer",
title: "Pending Review",
count: data?.reviewPending?.count,
amount: data?.reviewPending?.totalAmount,
icon: "bx bx-search-alt-2",
@ -88,11 +90,11 @@ const ExpenseStatus = () => {
].map((item, idx) => (
<div
key={idx}
className="report-list-item rounded-2 mb-4 bg-lighter px-2 py-3 cursor-pointer"
className="report-list-item rounded-2 mb-4 bg-lighter px-2 py-1 cursor-pointer"
onClick={() => handleNavigate(item?.status)}
>
<div className="d-flex align-items-center">
<div className="report-list-icon shadow-xs me-4">
<div className="report-list-icon shadow-xs me-2">
<span className="d-inline-flex align-items-center justify-content-center rounded-circle border p-2">
<i className={`${item?.icon} ${item?.iconColor} bx-lg`}></i>
</span>
@ -106,8 +108,8 @@ const ExpenseStatus = () => {
</div>
<div className="">
{" "}
<small className="text-muted fs-semibold text-royalblue text-md">
{item?.count}{" "}
<small className={`text-royalblue ${countDigit(item?.count) >= 3 ? "text-xl" : "text-2xl"} text-gray-500`}>
{item?.count }
</small>
<small className="text-muted fs-semibold text-royalblue text-md">
<i className="bx bx-chevron-right"></i>

View File

@ -96,8 +96,14 @@ export function localToUtc(dateString) {
export const formatCurrency = (amount, currency = "INR", locale = "en-US") => {
return new Intl.NumberFormat(locale, {
style: "currency",
notation: "compact",
compactDisplay: "short",
currency: currency,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(amount);
};
export const countDigit = (num) => {
return Math.abs(num).toString().length;
};