دليل شامل: دمج إحصائيات فيروس كورونا (COVID-19) في لوحة تحكم خرائط React باستخدام Gatsby
ملاحظة المحرر: يهدف هذا الدليل إلى تقديم عرض توضيحي ومفهوم عملي لكيفية استخدام البيانات الواقعية لبناء لوحات تحكم تفاعلية. على الرغم من أن البيانات المستخدمة هنا مستقاة من واجهة برمجة تطبيقات NovelCOVID API وتُعد دقيقة، إلا أننا ننصح بالاعتماد على مصادر موثوقة مثل لوحة تحكم جامعة جونز هوبكنز للتحليل الشامل والدقيق. سلامتكم أولاً، ابقوا في منازلكم!
ماذا سنبني في هذا الدليل؟
سنقوم بتوسيع مشروعنا السابق للوحة تحكم الخرائط التفاعلية، مضيفين إليها إحصائيات أساسية عن حالات فيروس كورونا (COVID-19) يمكننا استردادها من واجهة برمجة التطبيقات NovelCOVID API. للحصول على فكرة واضحة، إليكم لمحة عن النموذج الذي نستند إليه:

نموذج خريطة فيروس كورونا (COVID-19) مع إحصائيات لوحة التحكم.
على الرغم من أنه ليس إلزامياً إكمال الجزء الأول من هذا الدليل لتطبيق هذه المفاهيم، إلا أنه سيسهل عليك الأمر كثيراً ويساعدك في إعداد الخريطة الخاصة بلوحة التحكم. إذا كنت تفضل البدء من هناك، وهو ما أوصي به، فراجع أولاً مقال: How to create a Coronavirus (COVID-19) Dashboard & Map App with Gatsby and Leaflet.
تطبيق خرائط؟ نعم! إذا لم يسبق لك التعامل مع الخرائط من قبل، فلا تيأس! الأمر ليس بالصعوبة التي قد تتخيلها. إذا كنت تفضل البدء بأساسيات الخرائط، يمكنك قراءة المزيد حول كيفية عمل الخرائط أولاً.
المتطلبات الأساسية قبل البدء
لتطبيق هذا الدليل، ستحتاج بشكل أساسي إلى تطبيق React جاهز. سأعمل هنا على لوحة التحكم التي بنيناها سابقاً في دليلنا الأخير، والتي تتضمن خريطة لحالات فيروس كورونا (COVID-19) لكل دولة.

لوحة تحكم خريطة فيروس كورونا (COVID-19).
أوصي بالبدء بالدليل السابق، ولكن إذا كنت ترغب في تخطي جزء الخريطة والبدء من الصفر، فإن أسهل طريقة هي استخدام Create React App أو Gatsby أو Next.js.
الخطوة 1: تحديث آلية جلب البيانات والإحصائيات
للبدء في بناء لوحة تحكم الإحصائيات، سنقوم ببعض الأعمال التحضيرية لتغيير طريقة جلب البيانات. الهدف هو تغليف منطق طلباتنا بطريقة قابلة لإعادة الاستخدام، بحيث يمكننا استخدامها لكل من بيانات الدول وبيانات الإحصائيات الجديدة.
إنشاء React Hook جديد لجلب البيانات
لنبدأ بإنشاء React Hook جديد سيكون مسؤولاً عن جلب البيانات. للقيام بذلك، أنشئ ملفاً جديداً باسم useTracker.js داخل مجلد hooks، ثم أضف سطراً في ملف hooks/index.js لتصديره:
// New file src/hooks/useTracker.js
// This will be empty for now
// Inside hooks/index.js
export { default as useTracker } from './useTracker';
داخل ملف useTracker.js، سنقوم بإعداد منطق طلب البيانات. هذا الملف طويل، لذا تأكد من نسخه ولصقه بالكامل قبل أن نشرح وظيفته:
import { useEffect, useState } from 'react';
import axios from 'axios';
const API_HOST = 'https://corona.lmao.ninja/v2';
const ENDPOINTS = [
{ id: 'all', path: '/all', isDefault: true },
{ id: 'countries', path: '/countries' }
]
const defaultState = {
data: null,
state: 'ready'
}
const useTracker = ({ api = 'all' }) => {
const [tracker = {}, updateTracker] = useState(defaultState)
async function fetchTracker() {
let route = ENDPOINTS.find(({ id } = {}) => id === api);
if ( !route ) {
route = ENDPOINTS.find(({ isDefault } = {}) => !!isDefault);
}
let response;
try {
updateTracker((prev) => {
return {
...prev,
state: 'loading'
}
});
response = await axios.get(`${API_HOST}${route.path}`);
} catch (e) {
updateTracker((prev) => {
return {
...prev,
state: 'error',
error: e
}
});
return;
}
const { data } = response;
updateTracker((prev) => {
return {
...prev,
state: 'ready',
data
}
});
}
useEffect(() => {
fetchTracker()
}, [api])
return { fetchTracker, ...tracker }
};
export default useTracker;
لنبدأ بالشرح من الأعلى:
- نقوم باستيراد التبعيات: سنستخدم
hookيuseEffectوuseStateمنReactلإدارة طلباتنا. - نحدد الثوابت الافتراضية: لدينا نقطة نهاية أساسية لـ
APIلبياناتنا، وقائمة بنقاط النهاية المتاحة التي سنستخدمها، وكائن حالة سيخزن بياناتنا. - نُعرف
hookالخاص بناuseTracker: يتضمنhookوسيطاً واحداً هوapi، والذي سيسمح لنا بتحديد نقطة النهاية التي سنستخدمها لإجراء طلبنا. - نُعدّ مثيلاً للحالة (
state instance): سنرغب في تتبع بياناتنا التي تم جلبها، لذلك ننشئ مثيلاً للحالةtrackerيمكننا تحديثه. - أنشأنا دالة غير متزامنة
fetchTracker: سنستخدمها لإجراء طلبنا الفعلي. - داخل دالتنا: نبحث أولاً عن مسار
APIوننشئ عنوانURLالخاص بنا، ثم نحدّث مثيل حالتنا إلى حالة “تحميل” (loading)، ونحاول إجراء طلبنا، ونلتقط أي أخطاء إن وجدت، وأخيراً إذا كان الطلب ناجحاً، نحدّث حالتنا بتلك البيانات. - نقوم بتشغيل دالتنا: باستخدام
hookالـuseEffect، نقوم بتشغيل دالةfetchTrackerلإجراء الطلب. لدينا تبعية واحدة فقط وهيapi. هذا يعني أن الدالة ستعمل فقط في المرة الأولى وفي أي وقت تتغير فيه قيمةapiالتي نمررها. لن نغير هذه القيمة، ولكن قد يكون ذلك مفيداً في حالات أخرى إذا كنت تقوم بتغييرAPIالمستخدم ديناميكياً. - نعيد كائن الـ
trackerالخاص بنا: يتضمن الكائن المُعاد كلاً من بيانات الـtrackerودالةfetchTrackerالتي يمكننا استخدامها لإعادة جلب البيانات إذا أردنا ذلك.
وبذلك، أصبح لدينا hook جديد تماماً سيجلب البيانات من NovelCOVID API.
استخدام Hook الجديد لجلب البيانات
لاستخدام هذا hook، لننتقل إلى الملف src/pages/index.js، ونزيل استيراد axios إذا كان موجوداً، وبدلاً من ذلك نستورد hook الخاص بنا:
import { useTracker } from 'hooks';
باستخدام hook الخاص بنا، لنستبدل طلب بيانات الدول الأصلي. أولاً، أضف ما يلي إلى الجزء العلوي من مكون IndexPage:
const { data: countries = [] } = useTracker({ api: 'countries' });
const hasCountries = Array.isArray(countries) && countries.length > 0;
سيسمح لنا هذا بجلب بيانات الدول ومعرفة ما إذا كانت هناك أي نتائج.
بعد ذلك، لنستبدل طلبنا الأصلي. داخل دالة mapEffect، لنقم بإزالة طلب axios بالإضافة إلى الاستجابة، وكائن البيانات المفكك، والثابت hasData.

مقارنة الكود لتحديث تأثير الخريطة.
ثم، استبدل hasData بـ hasCountries:
if ( !hasCountries ) return;
واستبدل data بـ countries في كائن geoJson حيث نربط ميزاتنا:
features: countries.map((country = {}) => {
في هذه المرحلة، إذا قمت بالحفظ والتحديث، فلن تلاحظ أي فرق عما كان لديك سابقاً.
إضافة طلب لإحصائيات الحالات
الآن بعد أن أصبحنا نستخدم hook الـ useTracker لجلب بيانات الدول، فلنستخدمه أيضاً لجلب الإحصائيات. بجانب المكان الذي أعددنا فيه hook الـ useTracker سابقاً، لنضف طلباً آخر:
const { data: stats = {} } = useTracker({ api: 'all' });
وإذا أضفنا تعليمة console.log أدناه لنرى ما بداخل stats:
console.log('stats', stats);
يجب أن نرى كائن بيانات stats مطبوعاً في وحدة التحكم!

استخدام console.log لعرض إحصائيات فيروس كورونا (COVID-19).
يمكنك متابعة التغييرات في سجل الالتزامات.
الخطوة 2: إضافة الإحصائيات إلى لوحة التحكم
الآن بعد أن أصبحت بياناتنا متاحة للاستخدام، لنستخدمها! للبدء في إضافة إحصائياتنا إلى لوحة التحكم، لننشئ بنية بيانات تسمح لنا بتكوين البيانات التي نريد استخدامها بسهولة. للقيام بذلك، لننشئ أولاً مصفوفة جديدة تسمى dashboardStats أسفل hasCountries في الجزء العلوي من مكون الصفحة:
const dashboardStats = [];
داخل هذه المصفوفة، لنضف بعض الكائنات الجديدة التي تحدد بياناتنا التي نسحبها من كائن stats الذي طلبناه. للبدء، لنحاول إضافة:
const dashboardStats = [
{
primary: {
label: 'Total Cases',
value: stats?.cases
},
secondary: {
label: 'Per 1 Million',
value: stats?.casesPerOneMillion
}
},
{
primary: {
label: 'Total Deaths',
value: stats?.deaths
},
secondary: {
label: 'Per 1 Million',
value: stats?.deathsPerOneMillion
}
},
{
primary: {
label: 'Total Tests',
value: stats?.tests
},
secondary: {
label: 'Per 1 Million',
value: stats?.testsPerOneMillion
}
}
]
السبب في تقسيم هذا إلى مفاتيح primary و secondary هو أننا سنستخدم ذلك للتمييز بين الإحصائيات المتشابهة منطقياً والتي نريد تنسيقها بشكل مختلف قليلاً.
ملاحظة: إذا لم تكن على دراية بصيغة ?.، فهي تسمى Optional Chaining (السلسلة الاختيارية). تسمح لنا هذه الميزة بسلسلة خصائصنا دون القلق بشأن ما إذا كانت الكائنات موجودة. إذا كانت stats غير معرفة (undefined)، فستعيد ببساطة undefined بدلاً من إلقاء خطأ.
مع بيانات إحصائياتنا، لنضف المتتبع (tracker) إلى خريطتنا. لنقم بإزالة مكون <Map> الحالي ونضعه متداخلاً داخل div المتتبع بالطريقة التالية:
<div className= "tracker" >
< Map { ...mapSettings } />
< div className = "tracker-stats" >
< ul >
{ dashboardStats.map(({ primary = {}, secondary = {} }, i) => {
return (
< li key = { ` Stat- ${ i }`} className = "tracker-stat" >
{ primary.value && (
< p className = "tracker-stat-primary" >
{ primary.value }
< strong > { primary.label } </ strong >
</ p >)}
{ secondary.value && (
< p className = "tracker-stat-secondary" >
{ secondary.value }
< strong > { secondary.label } </ strong >
</ p >)}
</ li >
);
})}
</ ul >
</ div >
</div>
يجب أن يأتي هذا الكود مباشرة بعد مكون <Helmet> إذا كنت تتبع الخطوات.
لشرح ما نقوم به:
- نحن ننشئ
divباسم “tracker” سينظم إحصائياتنا. - ننقل مكون
<Mapالخاص بنا داخل هذا الـtracker. - ننشئ قسماً منفصلاً يسمى “
tracker-stats“. - داخل هذا القسم، ننشئ قائمة غير مرتبة (
ul). - داخل قائمتنا، نمر عبر جميع إحصائياتنا الموجودة في
dashboardStats. - لكل إحصائية، ننشئ عنصر قائمة جديد (
li) ونضم فقرتين اختياريتين تتضمنان بيانات إحصائياتنا الأساسية والثانوية.
بمجرد إعادة تحميل صفحتنا، يجب أن نرى الآن بعض الإحصائيات:

إضافة الإحصائيات الأولى إلى الصفحة.
الآن بعد أن أصبحت إحصائياتنا على الصفحة، لنعمل على جعلها تبدو وكأنها جزء من لوحة تحكم. لننشئ ملفاً جديداً باسم _tracker.scss داخل مجلد src/assets/stylesheets/components. بمجرد إنشاء هذا الملف، أضفه أيضاً إلى ملف src/assets/stylesheets/components/__components.scss:
@import "tracker";
مع ملف تنسيق المكون الجديد الجاهز، لنضف بعض التنسيقات إلى _tracker.scss:
.tracker-stats {
color: white;
background-color: $blue-grey-900;
border-top: solid 1px darken($blue-grey-900, 5);
ul {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
list-style: none;
padding: 0;
margin: 0;
}
}
.tracker-stat {
font-size: 2em;
text-align: center;
padding: .5em;
border-right: solid 1px darken($blue-grey-900, 5);
border-bottom: solid 1px darken($blue-grey-900, 5);
strong {
font-weight: normal;
color: $blue-grey-300;
}
}
.tracker-stat-primary {
margin: 0;
strong {
display: block;
font-size: .5em;
}
}
.tracker-stat-secondary {
font-size: .5em;
margin: .8em 0 0;
strong {
font-size: .8em;
margin-left: .4em;
}
}
في الكود أعلاه – نضيف الألوان والتأثيرات التنظيمية، مثل استخدام CSS Grid، للسماح بتنظيم بياناتنا بطريقة سهلة القراءة وذات مظهر جذاب! كما أننا نستخدم بعض متغيرات الألوان الموجودة مسبقاً في المشروع للحفاظ على تناسق استخدام الألوان.
بمجرد حفظ هذه التنسيقات وإعادة تحميل الصفحة، يجب أن تبدو أفضل بكثير:

إضافة إحصائيات الحالات إلى لوحة التحكم.
من هنا، لا تتردد في إضافة المزيد من الإحصائيات أو تعديلها حسب رغبتك. في النموذج الذي أنشأته، أضفت إحصائيات للحالات النشطة، والحالات الحرجة، والحالات المتعافية. إذا كنت ترغب في القيام بذلك، يمكنك مراجعة الالتزام (commit).
تابع التغييرات في سجل الالتزامات!
الخطوة 3: جعل البيانات سهلة القراءة للمستخدم
يمكن اعتبار بقية هذا الدليل اختيارية، ولكن في النهاية نريد أن يتمكن الأشخاص من قراءة هذه الإحصائيات، أليس كذلك؟ لذا، دعنا نجعل الأرقام أسهل قليلاً للقراءة.
أولاً، لنفتح ملف src/lib/util.js ونضيف هذه الدالة:
/**
* commafy
* @description Applies appropriate commas to large numbers
*/
export function commafy(value) {
let numberString = `${value}`;
numberString = numberString.split('');
numberString.reverse();
numberString = numberString.reduce((prev, current, index) => {
const shouldComma = (index + 1) % 3 === 0 && index + 1 < numberString.length;
let updatedValue = `${prev}${current}`;
if (shouldComma) {
updatedValue = `${updatedValue},`;
}
return updatedValue;
}, '');
numberString = numberString.split('');
numberString.reverse()
numberString = numberString.join('');
return numberString;
}
ستأخذ هذه الدالة رقماً وتحوله إلى سلسلة نصية تحتوي على فواصل. لشرح ما تفعله:
- تستقبل قيمة كوسيط. لاستخدامنا، ستكون هذه القيمة على الأرجح رقماً.
- تحول القيمة إلى سلسلة نصية. سنستخدم هذا للعمل على إضافة الفواصل إلى رقمنا.
- نقسم تلك السلسلة إلى مصفوفة ونعكسها. نريد عكسها لأن ذلك يجعل إضافة الفواصل أسهل اعتماداً على الفهرس.
- نستخدم دالة
reduceفيJavaScriptلإعادة إنشاء سلسلة الأرقام الخاصة بنا. بعد كل 3 أرقام، نريد إضافة فاصلة. - بمجرد حصولنا على قيمتنا الجديدة مع الفواصل، نريد إعادة عكسها. لذا نقسمها مرة أخرى، ونعكس مصفوفة الأحرف، ونعيد ربطها، وهذا ما نعيده.
والآن بعد أن أصبح لدينا دالة commafy، لنستخدمها. بالعودة إلى ملف src/pages/index.js، لنستورد دالتنا في الجزء العلوي من الصفحة:
import { commafy } from 'lib/util';
ثم، في مصفوفة dashboardStats الخاصة بنا، لنستبدل كل قيمة رقمية بتعبير ثلاثي ودالة ستحول رقمنا إذا كان متاحاً:
value: stats ? commafy(stats?.cases) : '-'
يتحقق هذا السطر مما إذا كانت stats موجودة. إذا كانت كذلك، نطبق دالة commafy على قيمة cases. إذا لم تكن موجودة، نعيد الرمز - للإشارة إلى أنها غير متاحة.
بمجرد تكرار هذه العملية لجميع أرقامنا، يمكننا الحفظ وإعادة تحميل الصفحة، ورؤية أرقامنا سهلة القراءة!

تنسيق الإحصائيات لتكون سهلة القراءة.
تابع التغييرات في سجل الالتزامات!
الخطوة 4: إضافة تاريخ آخر تحديث
أخيراً، نريد التأكد من أن الأشخاص على اطلاع دائم ويفهمون آخر مرة تم فيها تحديث هذه البيانات. لحسن الحظ، توفر واجهة برمجة التطبيقات (API) الخاصة بنا تاريخ آخر تحديث لنا، فلنستخدمه!
في أسفل div الـ "tracker" الخاص بنا، تحت tracker-stats، لنضف ما يلي:
<div className= "tracker-last-updated" >
< p > Last Updated: { stats?.updated } </ p >
</div>
ينشئ هذا قسماً جديداً نضمن فيه ببساطة خاصية updated من إحصائياتنا.
وإذا قمنا بالحفظ وإعادة تحميل الصفحة، يمكننا رؤية تاريخ آخر تحديث!

إضافة تاريخ آخر تحديث إلى لوحة التحكم.
ولكن كيف يمكننا حتى فهم ماهية هذا الرقم، ما لم تكن أنت الحاسوب الذي يزحف عبر منشور المدونة هذا؟ لذا، لنغيره إلى تنسيق سهل القراءة للمستخدم كما فعلنا مع أرقامنا.
داخل ملف src/lib/util.js الخاص بنا، لنضف دالة أخرى:
/**
* friendlyDate
* @description Takes in a date value and returns a friendly version
*/
export function friendlyDate(value) {
const date = new Date(value);
return new Intl.DateTimeFormat('en', {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric'
}).format(date);
}
تنشئ هذه الدالة كائن Date جديداً، ثم تستخدم واجهة برمجة تطبيقات International DateTimeFormat API في JavaScript لتحويله إلى تنسيق سهل القراءة!
بمجرد حفظ ذلك، لنستورده بجانب دالة commafy في الجزء العلوي من ملف src/pages/index.js:
import { commafy, friendlyDate } from 'lib/util';
ثم يمكننا تحديث الكود الخاص بنا بشكل مشابه لكيفية تحديث أرقامنا:
Last Updated: { stats ? friendlyDate(stats?.updated) : '-' }
وإذا قمنا بالحفظ وإعادة التحميل، سنراه بطريقة سهلة القراءة!

تنسيق تاريخ آخر تحديث.
أخيراً، يجب أن يبدو قسم "آخر تحديث" (last updated) متناسقاً مع بقية لوحة التحكم، لذا لنضف بعض التنسيقات الإضافية. داخل ملف _tracker.scss الذي كنا نعمل عليه سابقاً:
.tracker-last-updated {
color: white;
background-color: $blue-grey-900;
padding: .8em 0;
p {
color: $blue-grey-300;
font-size: .8em;
text-align: center;
margin: 0;
}
}
وبمجرد النقر على حفظ وتحديث المتصفح، سنحصل على إحصائيات لوحة التحكم الخاصة بنا مع وقت آخر تحديث!

لوحة التحكم النهائية مع تاريخ آخر تحديث منسق.
تابع التغييرات في سجل الالتزامات!
خطوات إضافية لتطوير لوحة التحكم
جعل بيانات تلميح المؤشر (Marker Tooltip) سهلة القراءة
الآن بعد أن أصبح لدينا دالتا commafy و friendlyDate المفيدتان، يمكننا إعادة استخدام هاتين الدالتين لتنظيف البيانات في النوافذ المنبثقة لمؤشرات الدول على الخريطة!
استخدام دالة fetchTracker لجلب التحديثات الدورية
داخل hook الـ useTracker الذي أنشأناه، قمنا بتصدير دالة تسمى fetchTracker. تتيح لنا هذه الدالة فرض طلب إلى API لجلب بيانات جديدة. للتأكد من أن خريطتنا تظل محدثة حتى عندما لا يقوم المستخدم بتحديث الصفحة، يمكننا إنشاء مؤقت في JavaScript لاستدعاء هذه الدالة بانتظام لتحديث بيانات لوحة التحكم الخاصة بنا.
مسح طبقات الخريطة قبل إضافة طبقات جديدة
أحد الأمور التي لا نقوم بها حالياً هو تنظيف الطبقات القديمة قبل إضافة طبقات جديدة. الطريقة التي تم بها إعداد الخريطة تجعلها تستمر في إضافة الطبقات فوق بعضها البعض. ما يمكننا فعله هو مسح الطبقات القديمة قبل إضافة جميع طبقاتنا الجديدة. راجع هذا الالتزام (commit) للبدء!
هل ترغب في معرفة المزيد عن الخرائط؟
يمكنك مراجعة بعض مصادري الأخرى للبدء:
- كيفية إنشاء لوحة تحكم وتطبيق خريطة لفيروس كورونا (COVID-19) في
ReactباستخدامGatsbyوLeaflet(الجزء الأول من هذا المقال). - كيفية إعداد نمط خريطة أساس
Mapboxمخصص باستخدامReact LeafletوLeaflet Gatsby Starter. - الجميع يمكنهم رسم الخرائط! إلهام ومقدمة إلى عالم رسم الخرائط.
- كيفية إنشاء تطبيق خريطة لرحلة صيفية باستخدام
GatsbyوLeaflet. - كيفية إنشاء متتبع سانتا الخاص بك باستخدام
GatsbyوReact Leaflet. - كيفية بناء تطبيق خرائط في
Reactبالطريقة السهلة باستخدامLeaflet.
الخلاصة التقنية
لقد استعرضنا في هذا الدليل خطوات عملية وفعالة لدمج إحصائيات فيروس كورونا (COVID-19) في لوحة تحكم خرائط تفاعلية مبنية باستخدام React و Gatsby. من خلال إنشاء React Hook مخصص لجلب البيانات وتنسيقها بشكل سهل القراءة، تمكنا من تقديم تجربة مستخدم غنية بالمعلومات. هذا النهج لا يعزز قابلية إعادة استخدام الكود فحسب، بل يضمن أيضاً عرض البيانات بطريقة واضحة ومحدثة، مما يجعله نموذجاً ممتازاً لتطوير لوحات التحكم التي تعتمد على البيانات الحية.