Recharts + Next.js App Router: بناء لوحات تحكم تفاعلية لعرض البيانات

Noqta Team
بواسطة Noqta Team ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

البيانات تحكي قصة — والمخططات تجعلها لا تُنسى. Recharts هي أشهر مكتبة رسوم بيانية مبنية أصلاً لـ React، تعتمد على مكونات قابلة للتركيب وتستخدم D3 في الخلفية. في هذا الدرس، ستبني لوحة تحكم كاملة لتحليلات المبيعات باستخدام Next.js 15 App Router، وستتعلم كل نوع مخطط ونمط تحتاجه للإنتاج.

ماذا ستتعلم

بنهاية هذا الدرس، ستكون قادراً على:

  • إعداد Recharts في مشروع Next.js 15 App Router مع TypeScript
  • بناء مخططات شريطية وخطية ومساحية ودائرية من الصفر
  • إنشاء تلميحات مخصصة ووسائل إيضاح تفاعلية
  • جعل المخططات متجاوبة بالكامل باستخدام ResponsiveContainer
  • جلب وعرض بيانات ديناميكية من مسارات API
  • إضافة حركات سلسة وتفاعلات عند التمرير
  • دعم الوضع الداكن بألوان مخططات متوافقة مع السمة
  • تنظيم كل شيء في تصميم لوحة تحكم جاهز للإنتاج

المتطلبات المسبقة

قبل البدء، تأكد من توفر:

  • Node.js 20+ مثبت (node --version)
  • خبرة في TypeScript (الأنواع، الواجهات، الأنواع العامة)
  • إلمام بأساسيات Next.js App Router (التخطيطات، الصفحات، مكونات العميل/الخادم)
  • فهم أساسي لـ Tailwind CSS لتنسيق التخطيط

لماذا Recharts في 2026؟

عندما يتعلق الأمر بالرسوم البيانية في React، تتنافس عدة مكتبات. إليك لماذا تبقى Recharts الخيار الأفضل:

الميزةRechartsChart.js (react-chartjs-2)NivoVictory
مكونات React أصليةنعمغلافنعمنعم
حجم الحزمة~45 KB~60 KB~50 KB~55 KB
قابلية التركيبممتازةمحدودةجيدةجيدة
دعم TypeScriptمدمججزئيمدمجمدمج
توافق SSRنعمCanvas (محدود)نعمنعم
التخصيصعلى مستوى المكونكائن إعداداتخصائصخصائص
منحنى التعلممنخفضمنخفضمتوسطمتوسط

تتميز Recharts لأن كل عنصر — المحاور، الشبكات، التلميحات، وسائل الإيضاح — هو مكون React يمكنك تركيبه وتنسيقه وتوسيعه. إذا كنت تعرف React، فأنت تعرف بالفعل كيف تستخدم Recharts.


الخطوة 1: إعداد المشروع

أنشئ مشروع Next.js 15 جديد مع TypeScript و Tailwind CSS:

npx create-next-app@latest sales-dashboard --typescript --tailwind --app --src-dir
cd sales-dashboard

ثبّت Recharts:

npm install recharts

يجب أن يكون هيكل مشروعك كالتالي:

sales-dashboard/
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── page.tsx
│   │   └── api/
│   │       └── sales/
│   │           └── route.ts
│   ├── components/
│   │   └── charts/
│   │       ├── BarChartCard.tsx
│   │       ├── LineChartCard.tsx
│   │       ├── AreaChartCard.tsx
│   │       ├── PieChartCard.tsx
│   │       └── CustomTooltip.tsx
│   └── lib/
│       └── data.ts
├── package.json
└── tsconfig.json

الخطوة 2: تعريف طبقة البيانات

أنشئ src/lib/data.ts مع بيانات نموذجية مكتوبة الأنواع للوحة التحكم:

export interface MonthlySales {
  month: string;
  revenue: number;
  orders: number;
  customers: number;
}
 
export interface ProductSales {
  name: string;
  value: number;
  color: string;
}
 
export const monthlySalesData: MonthlySales[] = [
  { month: "يناير", revenue: 4200, orders: 180, customers: 120 },
  { month: "فبراير", revenue: 5100, orders: 210, customers: 145 },
  { month: "مارس", revenue: 4800, orders: 195, customers: 130 },
  { month: "أبريل", revenue: 6300, orders: 270, customers: 180 },
  { month: "مايو", revenue: 5900, orders: 250, customers: 165 },
  { month: "يونيو", revenue: 7200, orders: 310, customers: 210 },
  { month: "يوليو", revenue: 6800, orders: 290, customers: 195 },
  { month: "أغسطس", revenue: 7500, orders: 320, customers: 225 },
  { month: "سبتمبر", revenue: 8100, orders: 350, customers: 240 },
  { month: "أكتوبر", revenue: 7800, orders: 335, customers: 230 },
  { month: "نوفمبر", revenue: 9200, orders: 400, customers: 280 },
  { month: "ديسمبر", revenue: 10500, orders: 450, customers: 320 },
];
 
export const productSalesData: ProductSales[] = [
  { name: "إلكترونيات", value: 35, color: "#6366f1" },
  { name: "ملابس", value: 25, color: "#8b5cf6" },
  { name: "المنزل والحديقة", value: 20, color: "#a78bfa" },
  { name: "رياضة", value: 12, color: "#c4b5fd" },
  { name: "كتب", value: 8, color: "#ddd6fe" },
];

يمنحك هذا مجموعتي بيانات: بيانات مبيعات شهرية زمنية وتوزيع منتجات حسب الفئات. كلاهما مكتوب الأنواع بالكامل لأمان TypeScript.


الخطوة 3: بناء مخطط شريطي

أنشئ src/components/charts/BarChartCard.tsx. سيعرض المخطط الشريطي الإيرادات الشهرية:

"use client";
 
import {
  BarChart,
  Bar,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  ResponsiveContainer,
} from "recharts";
import { MonthlySales } from "@/lib/data";
 
interface BarChartCardProps {
  data: MonthlySales[];
}
 
export default function BarChartCard({ data }: BarChartCardProps) {
  return (
    <div className="rounded-xl border bg-white p-6 shadow-sm dark:bg-gray-900 dark:border-gray-800">
      <h3 className="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
        الإيرادات الشهرية
      </h3>
      <ResponsiveContainer width="100%" height={300}>
        <BarChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
          <CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
          <XAxis
            dataKey="month"
            tick={{ fontSize: 12, fill: "#6b7280" }}
            axisLine={{ stroke: "#d1d5db" }}
          />
          <YAxis
            tick={{ fontSize: 12, fill: "#6b7280" }}
            axisLine={{ stroke: "#d1d5db" }}
            tickFormatter={(value) => `$${value / 1000}k`}
          />
          <Tooltip
            formatter={(value: number) => [`$${value.toLocaleString()}`, "الإيرادات"]}
            contentStyle={{
              backgroundColor: "#1f2937",
              border: "none",
              borderRadius: "8px",
              color: "#f9fafb",
            }}
          />
          <Bar
            dataKey="revenue"
            fill="#6366f1"
            radius={[4, 4, 0, 0]}
            animationDuration={800}
          />
        </BarChart>
      </ResponsiveContainer>
    </div>
  );
}

النقاط الرئيسية:

  • ResponsiveContainer يغلف كل مخطط لجعله متجاوباً. اضبط دائماً width="100%" وارتفاعاً ثابتاً.
  • CartesianGrid مع strokeDasharray ينشئ خطوط شبكة متقطعة دقيقة.
  • radius في Bar يدوّر الزوايا العلوية لمظهر عصري.
  • tickFormatter في YAxis يحول الأرقام الكبيرة إلى $4k، $8k، إلخ.

الخطوة 4: بناء مخطط خطي

أنشئ src/components/charts/LineChartCard.tsx لعرض الاتجاهات عبر الزمن:

"use client";
 
import {
  LineChart,
  Line,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  Legend,
  ResponsiveContainer,
} from "recharts";
import { MonthlySales } from "@/lib/data";
 
interface LineChartCardProps {
  data: MonthlySales[];
}
 
export default function LineChartCard({ data }: LineChartCardProps) {
  return (
    <div className="rounded-xl border bg-white p-6 shadow-sm dark:bg-gray-900 dark:border-gray-800">
      <h3 className="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
        الطلبات مقابل العملاء
      </h3>
      <ResponsiveContainer width="100%" height={300}>
        <LineChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
          <CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
          <XAxis
            dataKey="month"
            tick={{ fontSize: 12, fill: "#6b7280" }}
          />
          <YAxis tick={{ fontSize: 12, fill: "#6b7280" }} />
          <Tooltip
            contentStyle={{
              backgroundColor: "#1f2937",
              border: "none",
              borderRadius: "8px",
              color: "#f9fafb",
            }}
          />
          <Legend
            wrapperStyle={{ paddingTop: "12px" }}
            iconType="circle"
          />
          <Line
            type="monotone"
            dataKey="orders"
            stroke="#6366f1"
            strokeWidth={2}
            dot={{ fill: "#6366f1", r: 4 }}
            activeDot={{ r: 6, stroke: "#6366f1", strokeWidth: 2 }}
            animationDuration={1000}
            name="الطلبات"
          />
          <Line
            type="monotone"
            dataKey="customers"
            stroke="#10b981"
            strokeWidth={2}
            dot={{ fill: "#10b981", r: 4 }}
            activeDot={{ r: 6, stroke: "#10b981", strokeWidth: 2 }}
            animationDuration={1200}
            name="العملاء"
          />
        </LineChart>
      </ResponsiveContainer>
    </div>
  );
}

لاحظ كيف يمكنك تراكب عدة مكونات Line على نفس المخطط. كل واحد يحصل على لونه الخاص، ومكون Legend يلتقط أسماء dataKey تلقائياً. الخاصية type="monotone" تنشئ منحنيات سلسة بدلاً من زوايا حادة.


الخطوة 5: بناء مخطط مساحي بتدرجات لونية

أنشئ src/components/charts/AreaChartCard.tsx. المخططات المساحية مثالية لعرض الحجم عبر الزمن:

"use client";
 
import {
  AreaChart,
  Area,
  XAxis,
  YAxis,
  CartesianGrid,
  Tooltip,
  ResponsiveContainer,
} from "recharts";
import { MonthlySales } from "@/lib/data";
 
interface AreaChartCardProps {
  data: MonthlySales[];
}
 
export default function AreaChartCard({ data }: AreaChartCardProps) {
  return (
    <div className="rounded-xl border bg-white p-6 shadow-sm dark:bg-gray-900 dark:border-gray-800">
      <h3 className="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
        اتجاه الإيرادات
      </h3>
      <ResponsiveContainer width="100%" height={300}>
        <AreaChart data={data} margin={{ top: 5, right: 20, left: 10, bottom: 5 }}>
          <defs>
            <linearGradient id="revenueGradient" x1="0" y1="0" x2="0" y2="1">
              <stop offset="5%" stopColor="#6366f1" stopOpacity={0.3} />
              <stop offset="95%" stopColor="#6366f1" stopOpacity={0} />
            </linearGradient>
          </defs>
          <CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
          <XAxis
            dataKey="month"
            tick={{ fontSize: 12, fill: "#6b7280" }}
          />
          <YAxis
            tick={{ fontSize: 12, fill: "#6b7280" }}
            tickFormatter={(value) => `$${value / 1000}k`}
          />
          <Tooltip
            formatter={(value: number) => [`$${value.toLocaleString()}`, "الإيرادات"]}
            contentStyle={{
              backgroundColor: "#1f2937",
              border: "none",
              borderRadius: "8px",
              color: "#f9fafb",
            }}
          />
          <Area
            type="monotone"
            dataKey="revenue"
            stroke="#6366f1"
            strokeWidth={2}
            fill="url(#revenueGradient)"
            animationDuration={1000}
          />
        </AreaChart>
      </ResponsiveContainer>
    </div>
  );
}

السر هنا هو تدرجات SVG. كتلة defs تعرّف linearGradient يتلاشى من شفافية 30% في الأعلى إلى شفاف تماماً في الأسفل. مكون Area يشير إلى هذا التدرج عبر fill="url(#revenueGradient)". هذا ينشئ مظهراً احترافياً مصقولاً لا يمكن للتعبئة المسطحة تحقيقه.


الخطوة 6: بناء مخطط دائري

أنشئ src/components/charts/PieChartCard.tsx للبيانات الفئوية:

"use client";
 
import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, Legend } from "recharts";
import { ProductSales } from "@/lib/data";
 
interface PieChartCardProps {
  data: ProductSales[];
}
 
export default function PieChartCard({ data }: PieChartCardProps) {
  return (
    <div className="rounded-xl border bg-white p-6 shadow-sm dark:bg-gray-900 dark:border-gray-800">
      <h3 className="mb-4 text-lg font-semibold text-gray-900 dark:text-white">
        المبيعات حسب الفئة
      </h3>
      <ResponsiveContainer width="100%" height={300}>
        <PieChart>
          <Pie
            data={data}
            cx="50%"
            cy="50%"
            innerRadius={60}
            outerRadius={100}
            paddingAngle={3}
            dataKey="value"
            animationDuration={800}
            label={({ name, percent }) =>
              `${name} ${(percent * 100).toFixed(0)}%`
            }
            labelLine={{ stroke: "#6b7280" }}
          >
            {data.map((entry, index) => (
              <Cell key={`cell-${index}`} fill={entry.color} />
            ))}
          </Pie>
          <Tooltip
            formatter={(value: number) => [`${value}%`, "الحصة"]}
            contentStyle={{
              backgroundColor: "#1f2937",
              border: "none",
              borderRadius: "8px",
              color: "#f9fafb",
            }}
          />
          <Legend iconType="circle" />
        </PieChart>
      </ResponsiveContainer>
    </div>
  );
}

تعيين innerRadius ينشئ مخطط دونات بدلاً من دائرة كاملة. paddingAngle يضيف فجوات دقيقة بين الشرائح. كل مكون Cell يتيح لك تعيين ألوان فردية من بياناتك.


الخطوة 7: بناء تلميح مخصص

التلميحات الافتراضية تعمل جيداً، لكن التلميحات المخصصة تمنحك تحكماً كاملاً في التصميم. أنشئ src/components/charts/CustomTooltip.tsx:

"use client";
 
interface CustomTooltipProps {
  active?: boolean;
  payload?: Array<{
    name: string;
    value: number;
    color: string;
  }>;
  label?: string;
}
 
export default function CustomTooltip({
  active,
  payload,
  label,
}: CustomTooltipProps) {
  if (!active || !payload || payload.length === 0) {
    return null;
  }
 
  return (
    <div className="rounded-lg border border-gray-200 bg-white px-4 py-3 shadow-lg dark:border-gray-700 dark:bg-gray-800">
      <p className="mb-2 text-sm font-medium text-gray-900 dark:text-white">
        {label}
      </p>
      {payload.map((item, index) => (
        <div key={index} className="flex items-center gap-2 text-sm">
          <span
            className="inline-block h-3 w-3 rounded-full"
            style={{ backgroundColor: item.color }}
          />
          <span className="text-gray-600 dark:text-gray-400">{item.name}:</span>
          <span className="font-semibold text-gray-900 dark:text-white">
            {typeof item.value === "number"
              ? item.value.toLocaleString()
              : item.value}
          </span>
        </div>
      ))}
    </div>
  );
}

استخدمه في أي مخطط عن طريق استبدال Tooltip المضمن بـ:

<Tooltip content={<CustomTooltip />} />

التلميح المخصص يستقبل active و payload و label كخصائص من Recharts. يمكنك تقديم أي JSX تريده — فئات Tailwind، أيقونات، رسوم بيانية صغيرة، أي شيء يمكن لـ React تقديمه.


الخطوة 8: إنشاء مسار API للبيانات الديناميكية

في لوحة تحكم حقيقية، تأتي البيانات من API. أنشئ src/app/api/sales/route.ts:

import { NextResponse } from "next/server";
import { monthlySalesData, productSalesData } from "@/lib/data";
 
export async function GET() {
  // محاكاة تأخير API
  await new Promise((resolve) => setTimeout(resolve, 300));
 
  return NextResponse.json({
    monthly: monthlySalesData,
    products: productSalesData,
    summary: {
      totalRevenue: monthlySalesData.reduce((sum, m) => sum + m.revenue, 0),
      totalOrders: monthlySalesData.reduce((sum, m) => sum + m.orders, 0),
      totalCustomers: monthlySalesData.reduce((sum, m) => sum + m.customers, 0),
      avgOrderValue: Math.round(
        monthlySalesData.reduce((sum, m) => sum + m.revenue, 0) /
          monthlySalesData.reduce((sum, m) => sum + m.orders, 0)
      ),
    },
  });
}

يعيد مسار API هذا جميع بيانات لوحة التحكم في طلب واحد. في الإنتاج، ستستبدل البيانات الثابتة باستعلامات قاعدة البيانات أو استدعاءات API خارجية.


الخطوة 9: تجميع صفحة لوحة التحكم

الآن اجمع كل شيء معاً في src/app/page.tsx:

"use client";
 
import { useEffect, useState } from "react";
import BarChartCard from "@/components/charts/BarChartCard";
import LineChartCard from "@/components/charts/LineChartCard";
import AreaChartCard from "@/components/charts/AreaChartCard";
import PieChartCard from "@/components/charts/PieChartCard";
import { MonthlySales, ProductSales } from "@/lib/data";
 
interface DashboardData {
  monthly: MonthlySales[];
  products: ProductSales[];
  summary: {
    totalRevenue: number;
    totalOrders: number;
    totalCustomers: number;
    avgOrderValue: number;
  };
}
 
export default function DashboardPage() {
  const [data, setData] = useState<DashboardData | null>(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    fetch("/api/sales")
      .then((res) => res.json())
      .then((json) => {
        setData(json);
        setLoading(false);
      });
  }, []);
 
  if (loading || !data) {
    return (
      <div className="flex min-h-screen items-center justify-center">
        <div className="h-8 w-8 animate-spin rounded-full border-4 border-indigo-500 border-t-transparent" />
      </div>
    );
  }
 
  const stats = [
    { label: "إجمالي الإيرادات", value: `$${data.summary.totalRevenue.toLocaleString()}` },
    { label: "إجمالي الطلبات", value: data.summary.totalOrders.toLocaleString() },
    { label: "العملاء", value: data.summary.totalCustomers.toLocaleString() },
    { label: "متوسط قيمة الطلب", value: `$${data.summary.avgOrderValue}` },
  ];
 
  return (
    <main className="min-h-screen bg-gray-50 p-6 dark:bg-gray-950">
      <div className="mx-auto max-w-7xl">
        <h1 className="mb-8 text-3xl font-bold text-gray-900 dark:text-white">
          لوحة تحليلات المبيعات
        </h1>
 
        {/* بطاقات الملخص */}
        <div className="mb-8 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
          {stats.map((stat) => (
            <div
              key={stat.label}
              className="rounded-xl border bg-white p-6 shadow-sm dark:bg-gray-900 dark:border-gray-800"
            >
              <p className="text-sm text-gray-500 dark:text-gray-400">
                {stat.label}
              </p>
              <p className="mt-1 text-2xl font-bold text-gray-900 dark:text-white">
                {stat.value}
              </p>
            </div>
          ))}
        </div>
 
        {/* شبكة المخططات */}
        <div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
          <BarChartCard data={data.monthly} />
          <LineChartCard data={data.monthly} />
          <AreaChartCard data={data.monthly} />
          <PieChartCard data={data.products} />
        </div>
      </div>
    </main>
  );
}

تجلب هذه الصفحة البيانات من API، وتعرض إحصاءات ملخصة في الصف العلوي، وتعرض جميع أنواع المخططات الأربعة في شبكة متجاوبة من عمودين. على الهاتف المحمول، تتكدس المخططات عمودياً بفضل نقطة التوقف lg:grid-cols-2.


الخطوة 10: إضافة دعم الوضع الداكن

لا تملك Recharts وضعاً داكناً مدمجاً، لكن يمكنك جعلها متوافقة مع السمة باستخدام متغيرات CSS أو سياق React. إليك نهجاً خفيفاً باستخدام خطاف مخصص:

// src/lib/useChartTheme.ts
"use client";
 
import { useEffect, useState } from "react";
 
export interface ChartTheme {
  gridColor: string;
  tickColor: string;
  tooltipBg: string;
  tooltipText: string;
}
 
const lightTheme: ChartTheme = {
  gridColor: "#e5e7eb",
  tickColor: "#6b7280",
  tooltipBg: "#1f2937",
  tooltipText: "#f9fafb",
};
 
const darkTheme: ChartTheme = {
  gridColor: "#374151",
  tickColor: "#9ca3af",
  tooltipBg: "#f9fafb",
  tooltipText: "#1f2937",
};
 
export function useChartTheme(): ChartTheme {
  const [isDark, setIsDark] = useState(false);
 
  useEffect(() => {
    const mq = window.matchMedia("(prefers-color-scheme: dark)");
    setIsDark(mq.matches);
    const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
    mq.addEventListener("change", handler);
    return () => mq.removeEventListener("change", handler);
  }, []);
 
  return isDark ? darkTheme : lightTheme;
}

ثم استخدم السمة في مكونات المخططات:

import { useChartTheme } from "@/lib/useChartTheme";
 
export default function BarChartCard({ data }: BarChartCardProps) {
  const theme = useChartTheme();
 
  return (
    <ResponsiveContainer width="100%" height={300}>
      <BarChart data={data}>
        <CartesianGrid strokeDasharray="3 3" stroke={theme.gridColor} />
        <XAxis tick={{ fill: theme.tickColor }} />
        <YAxis tick={{ fill: theme.tickColor }} />
        <Tooltip
          contentStyle={{
            backgroundColor: theme.tooltipBg,
            color: theme.tooltipText,
          }}
        />
        <Bar dataKey="revenue" fill="#6366f1" />
      </BarChart>
    </ResponsiveContainer>
  );
}

الآن تتكيف مخططاتك تلقائياً عندما يتبدل المستخدم بين الوضع الفاتح والداكن.


الخطوة 11: تحسين الأداء

عند عرض العديد من المخططات في صفحة واحدة، ضع هذه النصائح في الاعتبار:

التحميل الكسول للمخططات أسفل الطية

اعرض المخططات فقط عندما تدخل في مجال الرؤية:

"use client";
 
import { useEffect, useRef, useState, ReactNode } from "react";
 
interface LazyChartProps {
  children: ReactNode;
  height?: number;
}
 
export default function LazyChart({ children, height = 400 }: LazyChartProps) {
  const ref = useRef<HTMLDivElement>(null);
  const [visible, setVisible] = useState(false);
 
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: "100px" }
    );
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);
 
  return (
    <div ref={ref} style={{ minHeight: height }}>
      {visible ? children : null}
    </div>
  );
}

غلّف أي مكون مخطط بـ LazyChart لتأجيل العرض حتى يدخل في نافذة العرض.

تقليل مدة الحركة

للوحات التحكم التي تحتوي على أكثر من 4 مخططات، فكر في تقليل animationDuration إلى 400ms أو تعطيل الحركات بالكامل مع isAnimationActive={false} لتسريع الرسم الأولي.

حفظ بيانات المخطط في الذاكرة

إذا كانت بياناتك مشتقة من استجابات API، غلّف التحويلات في useMemo لتجنب إعادة الحساب في كل عرض:

const chartData = useMemo(
  () => rawData.map((item) => ({ ...item, revenue: item.revenue / 100 })),
  [rawData]
);

استكشاف الأخطاء وإصلاحها

المخططات لا تظهر على الخادم

Recharts تستخدم DOM المتصفح ولن تظهر أثناء العرض من جانب الخادم. ضع دائماً علامة "use client" على مكونات المخططات وغلّفها بـ ResponsiveContainer. إذا رأيت أخطاء ترطيب، تأكد من أن مكون المخطط يُعرض فقط على جانب العميل.

ResponsiveContainer بارتفاع صفري

ResponsiveContainer يرث أبعاده من الحاوية الأم. إذا لم تكن للحاوية الأم ارتفاع محدد، ينهار المخطط. اضبط دائماً خاصية height على ResponsiveContainer أو تأكد من أن الحاوية الأم لها ارتفاع معرّف.

تومض التلميح على الهاتف

على أجهزة اللمس، يمكن أن تومض التلميحات لأن أحداث اللمس تعمل بشكل مختلف. اضبط allowEscapeViewBox على Tooltip لمنعه من القص عند حدود المخطط:

<Tooltip allowEscapeViewBox={{ x: true, y: true }} />

الخطوات التالية

الآن بعد أن أصبحت لوحة تحكمك فعالة، فكر في هذه التحسينات:

  • منتقي نطاق التاريخ — دع المستخدمين يصفون البيانات حسب فترات زمنية مخصصة
  • التصدير إلى PDF — استخدم html-to-image و jsPDF لتصدير لوحة التحكم
  • التحديثات في الوقت الفعلي — اتصل بـ WebSockets أو Server-Sent Events للبيانات الحية
  • مخططات التفصيل — انقر على شريط لرؤية بيانات تفصيلية
  • مخططات شريطية مكدسة ومجمعة — قارن عدة فئات جنباً إلى جنب
  • مكون الفرشاة — أضف محدد نطاق للتكبير في فترات زمنية محددة

تدعم Recharts كل هذا افتراضياً بنموذج المكونات القابلة للتركيب.


الخلاصة

في هذا الدرس، بنيت لوحة تحكم كاملة لتحليلات المبيعات باستخدام Recharts و Next.js App Router. تعلمت كيف:

  • إنشاء أربعة أنواع مختلفة من المخططات — شريطية وخطية ومساحية ودائرية
  • بناء تلميحات مخصصة لتجربة مستخدم مصقولة
  • جعل المخططات متجاوبة باستخدام ResponsiveContainer
  • جلب بيانات ديناميكية من مسارات Next.js API
  • دعم الوضع الداكن بألوان مخططات متوافقة مع السمة
  • تحسين الأداء بالتحميل الكسول وحفظ الذاكرة

Recharts خيار قوي لأنها تعامل كل عنصر مخطط كمكون React قابل للتركيب. تنسّقه وتوسّعه وتختبره بنفس الطريقة التي تتعامل بها مع أي مكون آخر في تطبيقك. اجمعه مع Tailwind CSS للتخطيط ولديك مجموعة أدوات لوحة تحكم تتوسع من النماذج الأولية إلى الإنتاج.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على Vite 6 + React + TypeScript: بناء تطبيق ويب حديث من الصفر في 2026.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة

إضافة المصادقة لتطبيق Next.js 15 باستخدام Auth.js v5: البريد الإلكتروني وOAuth والتحكم بالأدوار

تعلم كيفية إضافة نظام مصادقة جاهز للإنتاج لتطبيق Next.js 15 باستخدام Auth.js v5. يغطي هذا الدليل الشامل تسجيل الدخول عبر Google OAuth وبيانات الاعتماد بالبريد الإلكتروني وكلمة المرور والمسارات المحمية والتحكم بالوصول حسب الأدوار.

30 د قراءة·

بناء روبوت دردشة ذكاء اصطناعي محلي باستخدام Ollama و Next.js: الدليل الشامل

ابنِ روبوت دردشة ذكاء اصطناعي خاص يعمل بالكامل على جهازك المحلي باستخدام Ollama و Next.js. يغطي هذا الدليل العملي التثبيت والبث المباشر واختيار النماذج وبناء واجهة دردشة جاهزة للإنتاج — كل ذلك دون إرسال بياناتك إلى السحابة.

25 د قراءة·

بناء موقع محتوى متكامل باستخدام Payload CMS 3 و Next.js

تعلّم كيفية بناء موقع محتوى كامل الميزات باستخدام Payload CMS 3 الذي يعمل مباشرة داخل Next.js App Router. يغطي هذا الدرس المجموعات، محرر النصوص الغنية، رفع الوسائط، المصادقة، والنشر في بيئة الإنتاج.

30 د قراءة·