diff --git a/assets/admin/components/dashboard/TauriCharts.test.tsx b/assets/admin/components/dashboard/TauriCharts.test.tsx
new file mode 100644
index 00000000..18f5b4ba
--- /dev/null
+++ b/assets/admin/components/dashboard/TauriCharts.test.tsx
@@ -0,0 +1,48 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { TauriBarChart, TauriLineChart, type ChartPoint } from './TauriCharts';
+
+const zero: ChartPoint[] = Array.from({ length: 7 }, (_, i) => ({ label: `روز ${i}`, value: 0 }));
+const real: ChartPoint[] = [
+ { label: '۷ خرداد', value: 15 },
+ { label: '۸ خرداد', value: 40 },
+ { label: '۹ خرداد', value: 48 },
+];
+
+const EMPTY = 'دادهای برای نمایش نیست';
+
+describe('TauriBarChart', () => {
+ it('shows the empty state when every value is zero', () => {
+ render();
+ expect(screen.getByText(EMPTY)).toBeInTheDocument();
+ });
+
+ it('shows the empty state when there is no data', () => {
+ render();
+ expect(screen.getByText(EMPTY)).toBeInTheDocument();
+ });
+
+ it('renders the chart (x labels, no empty state) with real data', () => {
+ render();
+ expect(screen.queryByText(EMPTY)).not.toBeInTheDocument();
+ expect(screen.getByText('۸ خرداد')).toBeInTheDocument();
+ });
+});
+
+describe('TauriLineChart', () => {
+ it('shows the empty state when every value is zero', () => {
+ render();
+ expect(screen.getByText(EMPTY)).toBeInTheDocument();
+ });
+
+ it('shows the empty state with fewer than two points', () => {
+ render();
+ expect(screen.getByText(EMPTY)).toBeInTheDocument();
+ });
+
+ it('renders the chart with real data', () => {
+ const { container } = render();
+ expect(screen.queryByText(EMPTY)).not.toBeInTheDocument();
+ expect(container.querySelector('svg path')).toBeTruthy();
+ });
+});
diff --git a/assets/admin/components/dashboard/TauriCharts.tsx b/assets/admin/components/dashboard/TauriCharts.tsx
index 96ccfb06..9f36f252 100644
--- a/assets/admin/components/dashboard/TauriCharts.tsx
+++ b/assets/admin/components/dashboard/TauriCharts.tsx
@@ -13,7 +13,8 @@ export interface ChartPoint {
value: number;
}
-/** ~5 rounded gridline ticks covering [0, max], top → bottom. */
+/** ~5 rounded gridline ticks covering [0, max], top → bottom. Deduped so tiny
+ * integer ranges (e.g. max=1) never render repeated labels. */
function niceTicks(max: number, count = 4): number[] {
const safeMax = max > 0 ? max : 1;
const rawStep = safeMax / count;
@@ -21,10 +22,11 @@ function niceTicks(max: number, count = 4): number[] {
const norm = rawStep / mag;
const niceNorm = norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 5 ? 5 : 10;
const step = niceNorm * mag;
- const top = step * count;
const ticks: number[] = [];
for (let i = count; i >= 0; i--) ticks.push(Math.round(step * i));
- return ticks; // e.g. [400,300,200,100,0]
+ // collapse duplicate rounded labels (keeps gridlines proportional via ticks[0])
+ const deduped = ticks.filter((t, i) => i === 0 || t !== ticks[i - 1]);
+ return deduped.length >= 2 ? deduped : [ticks[0], 0];
}
const faNum = new Intl.NumberFormat('fa-IR');
@@ -85,7 +87,7 @@ function ChartFrame({
/** Bar chart — thin #5559CE columns (source: BarPlot, categoryGapRatio 0.7). */
export function TauriBarChart({ data }: { data: ChartPoint[] }) {
- if (!data.length) {
+ if (!data.length || !data.some((d) => d.value > 0)) {
return ;
}
const max = Math.max(...data.map((d) => d.value), 1);
@@ -115,7 +117,7 @@ export function TauriBarChart({ data }: { data: ChartPoint[] }) {
/** Line + area chart — #5559CE line over a #3A6FF8 gradient (source: LinePlot). */
export function TauriLineChart({ data }: { data: ChartPoint[] }) {
- if (data.length < 2) {
+ if (data.length < 2 || !data.some((d) => d.value > 0)) {
return ;
}
const values = data.map((d) => d.value);
diff --git a/assets/admin/components/dashboard/TauriDashboardView.tsx b/assets/admin/components/dashboard/TauriDashboardView.tsx
index 440d8a62..a360fada 100644
--- a/assets/admin/components/dashboard/TauriDashboardView.tsx
+++ b/assets/admin/components/dashboard/TauriDashboardView.tsx
@@ -40,7 +40,7 @@ function ChartTitle({ text }: { text: string }) {
function ChartCard({ title, selectorOptions, children }: { title: string; selectorOptions: string[]; children: React.ReactNode }) {
return (
-
+
@@ -75,12 +75,13 @@ export function TauriDashboardView({
formatRial,
}: TauriDashboardViewProps) {
return (
-
+
{/* Cards */}
- {/* Charts */}
-
+ {/* Charts — grid-2 (1fr 1fr, collapses to 1fr on narrow) for reliable
+ full-width equal columns, matching the rest of the admin dashboard */}
+
@@ -90,7 +91,7 @@ export function TauriDashboardView({
{/* New appointments list */}
-