لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 02/08/24 في كل الموقع
-
اريد ان اعرف كيف اضيف قيمة المتغير للسطر هذا c.execute("DELETE FROM names WHERE name =?") بدلا من علامة الاستفهام الكود وظيفته اضافة بعض الاسماء و حذف الاسم الذى دخله المستخدم import sqlite3 name = input("Enter name that you want to delete: \n") db = sqlite3.connect("names.db") c = db.cursor() c.execute("CREATE TABLE IF NOT EXISTS names(name TEXT)") c.execute("INSERT INTO names(name) VALUES('omar')") c.execute("INSERT INTO names(name) VALUES('ahmed')") c.execute("INSERT INTO names(name) VALUES('mhmed')") c.execute("DELETE FROM names WHERE name = ?") data = c.execute("SELECT name FROM names") for row in data: print(row) db.commit() db.close()2 نقاط
-
2 نقاط
-
DMM.DB.TBL_SUPP P = new DMM.DB.TBL_SUPP(); P.SUPP_NAME = edt_name.Text; P.SUPP_ADDRESS = edt_address.Text; ممكن طريقة اجيب بيها maxid وينزل فى تكس بوكس txtSupplierID فى مرحلة الاضافة2 نقاط
-
هل من الضروري حفظ كيفية كتابة الكود واستخدام الكلمات المفتاحية او الكلملت المحجوزة ام نكتفي فقط بمعرفة ما هي استخدام هذه الكلمات وعند كتابة الكود يكون نسخ الكود المناسب ولصقه في البرنامج ؟؟؟1 نقطة
-
لماذا لم نضع علامات مجال الfor هاذي المره {}و قد جربت ذالك و ضهر لي فقط اول رقم من الارقام التي مررته ك معامل1 نقطة
-
وضعت صورة لعنصر قائمة ul عن طريق list-style-image ولكن الصورة تأخد مساحة كبيرة جدا ولم استطع تغيير حجمها عن طريق ال height او width لماذا وكيف يمكنني ذلك هذا كود ال html <doctype html> <head> <title>test</title> <link rel="stylesheet" href="style.css"> </head> <body> <ul> <li>first item</li> <li>second item</li> <li>third item</li> </ul> </body> </html> وهنا كود ال css ul{ list-style-image: url(images/book.jpg); height: 50px; }1 نقطة
-
في مشروع تحليل المشاعر في النصوص العربية الموجود في كتاب ١٠ مشاريع عملية في الذكاء الإصطناعي الصادر عن أكاديمية حاسوب وفي أحد الخلايا ظهرت لي هذه المشكلة: وهذا هو الكود الخاص بها: نص الكود: # تحتاج وقت للتنفيذ from sklearn.model_selection import GridSearchCV from keras.wrappers.scikit_learn import KerasClassifier # حساب القيم الأمثلية للمعاملات المترفعة model = KerasClassifier(build_fn = create_model, epochs = 25, batch_size=128) # بعص القيم الممكنة للمعاملات المترفعة embed_dim = [32, 64] hidden_unit = [16, 32, 64] dropout_rate = [0.2] optimizers = [Adam, RMSprop] learning_rate = [0.01, 0.001, 0.0001] epochs = [10, 15, 25 ] batch_size = [128, 256] param_grid = dict(embed_dim = embed_dim, hidden_unit = hidden_unit, dropout_rate = dropout_rate, learning_rate = learning_rate, optimizers = optimizers, epochs = epochs, batch_size = batch_size) # تقويم النموذج لاختيار أفضل القيم grid = GridSearchCV(estimator = model, param_grid = param_grid, cv = 3) grid_result = grid.fit(X_train, y_train) results = pd.DataFrame() results['means'] = grid_result.cv_results_['mean_test_score'] results['stds'] = grid_result.cv_results_['std_test_score'] results['params'] = grid_result.cv_results_['params'] print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_)) # حفظ النتائج results.to_csv(r'gridsearchcv_results.csv', index = False, header = True) results.sort_values(by='means', ascending = False).reset_index(drop=True) وهذه هي المكتبات المحملة لدي: Package Version ---------------------------- --------------- absl-py 2.1.0 anyio 4.2.0 arabic-reshaper 3.0.0 argon2-cffi 23.1.0 argon2-cffi-bindings 21.2.0 arrow 1.3.0 asttokens 2.4.1 astunparse 1.6.3 async-lru 2.0.4 attrs 23.2.0 Babel 2.14.0 beautifulsoup4 4.12.3 bleach 6.1.0 cachetools 5.3.2 certifi 2024.2.2 cffi 1.16.0 charset-normalizer 3.3.2 click 8.1.7 colorama 0.4.6 comm 0.2.1 contourpy 1.2.0 cycler 0.12.1 debugpy 1.8.0 decorator 5.1.1 defusedxml 0.7.1 dm-tree 0.1.8 exceptiongroup 1.2.0 executing 2.0.1 fastjsonschema 2.19.1 flatbuffers 23.5.26 fonttools 4.47.2 fqdn 1.5.1 gast 0.5.4 glcontext 2.5.0 google-auth 2.27.0 google-auth-oauthlib 1.2.0 google-pasta 0.2.0 grpcio 1.60.1 h5py 3.10.0 idna 3.6 ipykernel 6.29.0 ipython 8.21.0 ipywidgets 8.1.1 isoduration 20.11.0 jedi 0.19.1 Jinja2 3.1.3 joblib 1.3.2 json5 0.9.14 jsonpointer 2.4 jsonschema 4.21.1 jsonschema-specifications 2023.12.1 jupyter 1.0.0 jupyter_client 8.6.0 jupyter-console 6.6.3 jupyter_core 5.7.1 jupyter-events 0.9.0 jupyter-lsp 2.2.2 jupyter_server 2.12.5 jupyter_server_terminals 0.5.2 jupyterlab 4.0.12 jupyterlab_pygments 0.3.0 jupyterlab_server 2.25.2 jupyterlab-widgets 3.0.9 keras 2.15.0 Keras-Preprocessing 1.1.2 kiwisolver 1.4.5 libclang 16.0.6 Markdown 3.5.2 markdown-it-py 3.0.0 MarkupSafe 2.1.5 matplotlib 3.8.2 matplotlib-inline 0.1.6 mdurl 0.1.2 metrics 0.3.3 mistune 3.0.2 ml-dtypes 0.2.0 mlxtend 0.23.1 moderngl 5.10.0 moderngl-window 2.4.5 multipledispatch 1.0.0 namex 0.0.7 nbclient 0.9.0 nbconvert 7.14.2 nbformat 5.9.2 nest-asyncio 1.6.0 nltk 3.8.1 notebook 7.0.7 notebook_shim 0.2.3 numpy 1.26.3 oauthlib 3.2.2 opt-einsum 3.3.0 overrides 7.7.0 packaging 23.2 pandas 2.2.0 pandocfilters 1.5.1 parso 0.8.3 pathlib2 2.3.7.post1 pathspec 0.5.5 Pillow 10.0.1 pip 23.3.2 platformdirs 4.2.0 prometheus-client 0.19.0 prompt-toolkit 3.0.43 protobuf 4.23.4 psutil 5.9.8 pure-eval 0.2.2 pyasn1 0.5.1 pyasn1-modules 0.3.0 pycparser 2.21 pyglet 2.0.10 Pygments 2.17.2 pyparsing 3.1.1 PyQt5 5.15.10 PyQt5-Qt5 5.15.2 PyQt5-sip 12.13.0 pyrr 0.10.3 python-bidi 0.4.2 python-dateutil 2.8.2 python-json-logger 2.0.7 pytz 2024.1 pywin32 306 pywinpty 2.0.12 PyYAML 6.0.1 pyzmq 25.1.2 qtconsole 5.5.1 QtPy 2.4.1 referencing 0.33.0 regex 2023.12.25 requests 2.31.0 requests-oauthlib 1.3.1 rfc3339-validator 0.1.4 rfc3986-validator 0.1.1 rich 13.7.0 rpds-py 0.17.1 rsa 4.9 scikit-learn 1.4.0 scipy 1.12.0 seaborn 0.13.2 Send2Trash 1.8.2 setuptools 58.1.0 six 1.16.0 sniffio 1.3.0 snowballstemmer 2.2.0 soupsieve 2.5 stack-data 0.6.3 tensorboard 2.15.1 tensorboard-data-server 0.7.2 tensorboard-plugin-wit 1.8.1 tensorflow 2.15.0 tensorflow-estimator 2.15.0 tensorflow-intel 2.15.0 tensorflow-io-gcs-filesystem 0.31.0 termcolor 2.4.0 terminado 0.18.0 threadpoolctl 3.2.0 tinycss2 1.2.1 tomli 2.0.1 tornado 6.4 tqdm 4.66.1 traitlets 5.14.1 types-python-dateutil 2.8.19.20240106 typing_extensions 4.9.0 tzdata 2023.4 uri-template 1.3.0 urllib3 2.2.0 wcwidth 0.2.13 webcolors 1.13 webencodings 0.5.1 websocket-client 1.7.0 Werkzeug 3.0.1 wheel 0.42.0 widgetsnbextension 4.0.9 wordcloud 1.9.3 wrapt 1.14.1 @Mustafa Suleiman @Hikmat Jaafer @حمزة عباد1 نقطة
-
السلام عليكم ورحمة الله وبركاتة اعمل على موقع عربي واريد ترجمته للإنجليزية عند الضغط على الأيقونة الخاصة كيف ؟1 نقطة
-
أريد تخزين ref of DOM element في redux slice ولكن اواجه مشكلة تخزين non-serializable data في slice فحاولت حلها ب json.stringify ولكنها فشلت. فهل من حل؟1 نقطة
-
السلام عليكم انا مبتدئ في البرمجة وسجلت في دورة واجهات المستخدم ولا اعرف شي عن البرمجة فما هي انواعها وما هو الفرق بين backed و frontend1 نقطة
-
لحذف صف من الجدول في مكتبة sqlite3، يمكنك استخدام الأمر DELETE FROM كما فعلت بالفعل. لكن لحذف صف معين، يجب تحديد الشرط الذي يحدد الصف الذي تريد حذفه. في حالتك، تستخدم علامة الاستفهام كبديل للقيمة التي ستقوم بتمريرها للشرط. إذا أردت استبدال علامة الاستفهام بقيمة معينة، يمكنك تمرير القيمة المراد حذفها كجزء من العملية التحضيرية (Prepared Statement)، وذلك بتمرير القيمة كمتغير إضافي في الأمر execute. هذه الكود سيحل لك المشكله ببساطه : import sqlite3 name_to_delete = input("Enter name that you want to delete: \n") db = sqlite3.connect("names.db") c = db.cursor() c.execute("CREATE TABLE IF NOT EXISTS names(name TEXT)") c.execute("INSERT INTO names(name) VALUES('omar')") c.execute("INSERT INTO names(name) VALUES('ahmed')") c.execute("INSERT INTO names(name) VALUES('mhmed')") c.execute("DELETE FROM names WHERE name = ?", (name_to_delete,)) data = c.execute("SELECT name FROM names") for row in data: print(row) db.commit() db.close() في هذا الكود، يتم استخدام name_to_delete لتخزين الاسم الذي يريد المستخدم حذفه. ثم يتم تمرير هذا الاسم كقيمة للشرط في الأمر DELETE FROM بواسطة العلامة ?، ويتم تمرير القيمة المراد حذفها كمتغير إضافي في الدالة execute باستخدام tuple (name_to_delete,).1 نقطة
-
بهذه الطريقة يمكنك وضع اى اسم تريده مكان name c.execute("DELETE FROM names WHERE name = ?", (name,))1 نقطة
-
DMM.DB.TBL_SUPP P = new DMM.DB.TBL_SUPP(); //txtSupplierID.Text=??? P.SUPP_NAME = edt_name.Text; P.SUPP_ADDRESS = edt_address.Text; P.SUPP_CIRTY = edt_cirty.Text; P.SUPP_PHONE = edt_phone.Text; P.SUPP_EMAIL = edt_mail.Text; P.SUPP_NOTS = edt_note.Text; P.SUPP_BANK_NAME = edt_bank_name.Text; P.SUPP_ACCOUNT_NUMBER = edt_account_name.Text; P.SUPP_BANK = edt_bank.Text; P.SUPP_BANK_IBAN = edt_iban.Text; P.SUPP_BANK_AREA = edt_bank_area.Text; P.SUPP_DATE = DateTime.Now; db.TBL_SUPP.Add(P); db.SaveChanges(); CLEAR(); كود اضافة مورد جديد وعندى txtSupplierID.Text عايز اجيب maxid اوتاميتك من التابل اللى يخص المورد واضيفو فى التكس بوكس1 نقطة
-
أولاً عليك الحصول على max ID من الجدول: int maxId; using (var context = new DMMEntities()) { maxId = context.TBL_SUPP.Max(s => s.SUPP_ID) + 1; } ثم اضبط max ID في المورد الجديد: P.SUPP_ID = maxId; بعدها أضف المورد إلى قاعدة البيانات: db.TBL_SUPP.Add(P); db.SaveChanges(); ثم عرض max ID في textbox: txtSupplierID.Text = maxId.ToString(); ومن الأفضل استخدام try-catch block لمعالجة أي أخطاء قد تحدث عند استخراج max ID، والجدير بالذكر أنك تستطيع الإعتماد على stored procedure لتنفيذ عملية الإضافة والحصول على max ID في خطوة واحدة، واستخدام LINQ لكتابة استعلام أكثر إيجازًا للحصول على max ID.1 نقطة
-
لا هو جلب جميع البيانات ولكن وضعهم فى سطر واحد . حاول ان تشغل هذا الملف فى قاعدة بيانات اخرى ستجد جميع الملفات قد تم استيرادها لديك1 نقطة
-
1 نقطة
-
ووالديك اللهم امين. نعم النسخ الاحتياطى يتم على حسب المدة اللتى تريدها وعلى حسب كمية البيانات المدخله . ممكن ان تقوم بعمل النسخ الاحتياطى يوميا مثلا او اسبوعيا او شهريا على حسب كم البيانات لديك . ولكن لا احبذ ان تقوم باخذ نسخه احتياطية كل ما يتم ادخال قيمة واحده او عدة قيم . ونعم النسخ الاحتياطى يقوم اولا بانشاء الجدول ثم انشاء جملة اضافه لجميع البيانات فى هذا الجدول . اما اذا كنت لا تريد ان يقوم بانشاء الجداول وتريد فقط القيم يمكنك اضافة هذه الجملة بعد اسم قاعدة البيانات . --no-create-info1 نقطة
-
في جدول العرض أريد أن يتم عرض الخريطة من خلال إرسال قيم خط الطول والعرض عند النقر على زر فتح ال modal <td> <button type="button" data-title="المنشأة" class="show-map-modal dropdown-item"> <i class="fa fa-image text-secondary" style="font-size: 20px"></i> </button> @include('dashboard.admin.show_maps') </td> ملف show_maps.js لفتح ال modal <script src="{{ asset('/js/leaflet/show_maps.js') }}"></script> document.querySelectorAll(".show-map-modal").forEach((item) => { item.addEventListener("click", (e) => { var lat = '{{ $tent->latitude }}'; var long = '{{ $tent->longitude }}'; function showMap(lat, long){ var coord = { lat:lat, lng:long}; map = new google.maps.Map(document.getElementById("map"), { center: coord, zoom: 8, scrollwheel: true, }); } showMap(0, 0); $("#map_modal").modal("show"); }); }); ملف show_maps.blade.php <!-- Start Modal map --> <div class="modal fade" id="map_modal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="map_modal" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h4 class="modal-title" id="delete_header">عرض محتوى الموقع</h4> <i type="reset" data-bs-dismiss="modal" aria-label="Close" class="las las la-times text-danger scale5 cancel-modal"></i> </div> <div class="modal-body pb-4"> <label class="form-label">الموقع</label> <input type="text" readonly="true" class="form-control" value="{{ $tent->location }}"></input> <div class="mb-3 col-md-12"> <div id="map" style="height: 300px;"></div> </div> </div> </div> </div> </div> <!-- End Modal map --> كل سجل ينبغي أن يتم عرض الخريطة الخاصة به1 نقطة
-
يمكنك استخدام خاصية ال data attribute فى الجافا سكريبت حيث يمكنك اضافة data attribute بالاسم الذى تريده لعنصر ال HTML ومن خلال الجافا سكريبت يمكنك قراءة هذه البيانات . هنا فى الزر الخاص بفتح ال modal نقوم باضافة اثنين من ال data attribute و نقوم بتمرير القيم الخاصة بخطوط الطول وخطوط العرض . <button type="button" data-title="المنشأة" data-latitude="{{$latitude}}" data-longitude="{{$longitude}}" class="show-map-modal dropdown-item"> <i class="fa fa-image text-secondary" style="font-size: 20px"></i> </button> وبواسطة الجافاسكريبت يمكننا قراءة هذه البيانات عن طريق خاصية ال dataset property ويكون مثل هذا اذا سنقوم بتغير هذا السطر فى ملف ال show_maps.js الى هذا showMap(element.dataset.latitude, element.dataset.longitude); وسيعمل جيدا ان شاء الله1 نقطة
-
أفهم أنك تريد أن يتم عرض الخريطة من خلال إرسال قيم خط الطول والعرض عند النقر على زر فتح ال modal، لتحقيق ذلك، يمكنك تعديل ملف show_maps.js لتمرير قيم خط الطول والعرض عند النقر على زر "فتح ال modal" واستخدامها لعرض الخريطة داخل ال modal. بهذا الشكل: document.querySelectorAll(".show-map-modal").forEach((item) => { item.addEventListener("click", (e) => { var lat = parseFloat(item.dataset.latitude); // الحصول على قيمة خط العرض var long = parseFloat(item.dataset.longitude); // الحصول على قيمة خط الطول function showMap(lat, long){ var coord = { lat: lat, lng: long }; map = new google.maps.Map(document.getElementById("map"), { center: coord, zoom: 8, scrollwheel: true, }); } showMap(lat, long); // عرض الخريطة بالقيم المحددة $("#map_modal").modal("show"); }); }); ثم، في جدول العرض، يجب عليك تحديد قيم خط الطول والعرض لكل صف وتمريرها كقيم لزر "فتح ال modal". بهذه الطريقة المبينة: <td> <button type="button" class="show-map-modal dropdown-item" data-latitude="{{ $tent->latitude }}" data-longitude="{{ $tent->longitude }}"> <i class="fa fa-image text-secondary" style="font-size: 20px"></i> </button> </td> ويجب التأكد من أن $tent->latitude و $tent->longitude تحتوي على قيم خط الطول والعرض الصحيحة لكل صف ومع هذه التغييرات، سيتم تمرير القيم المحددة لخط الطول والعرض إلى وظيفة JavaScript، وستظهر الخريطة المتعلقة بتلك القيم داخل ال modal مباشرة.1 نقطة
-
انا مسجل في الحساب و متاكد و مع ذلك لم يظهر شي و توصلت مع الدعم و لم يردوا ليوم و نصف هل هناك اي حل1 نقطة
-
1 نقطة
-
نفس المشكلة عندى فى جهاز asus rouge strix g15 rayzen7 2022?1 نقطة
-
سازودك بتفاصيل عند تنفيذها تستطيعي ترجمة اي موقع فقط باللغاتك الاساسيه html & css & js سنستعمل حزمة تسمي i18next هي إحدى مكتبات جافا سكريبت المشهورة للتعريب تسمى i18next. فهو يوفر حلاً قويًا للتعامل مع الترجمه و التعريب في مواقع الويب. فيما يلي مثال أساسي لكيفية استخدام i18next للترجمة: أولاً، قم بتضمين المكتبة في مشروعك. يمكنك تضمينه عبر CDN. يجب وضع هذا الكود في جميع الصفحات التي ستستخدم بها الترجمه <script src="https://cdnjs.cloudflare.com/ajax/libs/i18next/22.4.4/i18next.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/i18next-browser-languagedetector/6.0.1/i18nextBrowserLanguageDetector.min.js"></script> قم بإعداد ملفات الترجمة الخاصة بك. تحتوي هذه الملفات عادةً على ترجمات للغات مختلفة بتنسيق JSON. هذه مثال كامل للطريقه <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous" /> <title>Multi Language Translation</title> </head> <body> <div class="container"> <nav class="navbar navbar-expand-lg navbar-light justify-content-center"> <div> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" lang="en" data-i18n="home">Home</a> </li> <li class="nav-item"> <a class="nav-link" data-i18n="about">About</a> </li> <li class="nav-item"> <a class="nav-link" data-i18n="contact">Contact us</a> </li> </ul> </div> </nav> <h3 class="text-primary" data-i18n="selectLanguage">Select a language</h3> <select class="p-1 mt-2"> <option value="en" data-i18n="english" selected>English</option> <option value="ar" data-i18n="arabic">Arabic</option> </select> </div> <script src="script.js" type="module"></script> </body> </html> كود JavaScript (script.js) واعدادت ملف الترجمه : const translations = { en: { home: "Home", about: "About", contact: "Contact us", selectLanguage: "Select a language", english: "English", arabic: "Arabic", }, ar: { home: "الصفحة الرئيسية", about: "من نحن", contact: "تواصل معنا", selectLanguage: "إختر لغة", english: "الانجليزية", arabic: "العربية", }, }; const languageSelector = document.querySelector("select"); languageSelector.addEventListener("change", (event) => { setLanguage(event.target.value); localStorage.setItem("lang", event.target.value); }); document.addEventListener("DOMContentLoaded", () => { const language = localStorage.getItem("lang") || "en"; // اذا لم تكن اللغة متوفرة استخدم الانجليزية setLanguage(language); }); const setLanguage = (language) => { const elements = document.querySelectorAll("[data-i18n]"); elements.forEach((element) => { const translationKey = element.getAttribute("data-i18n"); element.textContent = translations[language][translationKey]; }); document.dir = language === "ar" ? "rtl" : "ltr"; }; الكود الذي قدمته يستخدم مكتبة i18next لإضافة دعم لترجمة المحتوى في الموقع . هذا الكود يقوم بتكوين i18next لاستخدام مكون مُحدد يسمى i18nextBrowserLanguageDetector لاكتشاف لغة المستخدم من خلال المتصفح. ثم يتم تهيئة i18next لتحميل الموارد (الترجمات) للغتين الإنجليزية والعربية. .use(i18nextBrowserLanguageDetector) : هذا الجزء يعين مكون الكشف عن لغة المتصفح لـ i18next. .init({ ... }): هنا يتم تهيئة i18next باستخدام الموارد (الترجمات) للغات المختلفة. كل لغة لها مفتاح وتحتوي على مصفوفة من الترجمات للمفاتيح المحددة. يتم تعريف fallbackLng كلغة احتياطية في حالة عدم توفر الترجمات للغة المطلوبة. يتم تعيين debug إلى true لتمكين وضع التصحيح، وdetection يحدد أسلوب الكشف عن اللغة (في هذه الحالة، يتم استخدام كشف اللغة من المتصفح). آخر جزء في الكود يقوم بإضافة مستمع لحدث النقر على الزر changeLanguageBtn. عندما يتم النقر على الزر، يتم التبديل بين اللغتين الإنجليزية والعربية باستخدام i18next.changeLanguage() وفقًا للغة الحالية.1 نقطة
-
ساشرح طريقه بسيطه تستطيع منها ترجمة موقعك ببساطه جدا واذا لديك اي استفسار فاتركه في التعليقات وساجاوبك فورا مثال صفحة html بسيط جدا بها مثلا 6 كلمات باللغه الانجليزيه و زر عند الضغط عليه يتم تحويل الموقع للعربي والعكس <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Translation Example</title> <link rel="stylesheet" href="styles.css"> </head> <body> <div id="words"> <p class="english-word">Hello</p> <p class="english-word">World</p> <p class="english-word">Good</p> <p class="english-word">Morning</p> </div> <button id="translateBtn">Translate</button> <script src="script.js"></script> </body> </html> ضع تصميم ليسه له علاقه ولاكن لاعطاء الصفحه جمالا body { font-family: Arial, sans-serif; } #words { margin-bottom: 20px; } .english-word { display: inline-block; margin-right: 10px; } و اخير تنفيذ المنطق الخاص بالترجمه const englishWords = ["Hello", "World", "Good", "Morning"]; const arabicWords = ["مرحبا", "العالم", "جيد", "صباح الخير"]; let isEnglish = true; document.getElementById('translateBtn').addEventListener('click', function() { const words = document.querySelectorAll('.english-word'); if (isEnglish) { words.forEach((word, index) => { word.textContent = arabicWords[index]; }); isEnglish = false; this.textContent = 'Translate to English'; } else { words.forEach((word, index) => { word.textContent = englishWords[index]; }); isEnglish = true; this.textContent = 'Translate to Arabic'; } }); هذه المثال يعطيكي الفكره ولاكن اذا كان موقك كبير جدا فا سنتجه الي استخدام الحزم الترجمه الخاصه js تستطيع تجربة الكود من هنا1 نقطة
-
نعم يدخل فى مهام مطور واجهة المستخدم. اذا كنت تريد عمله باستخدام مكتبة bootstrap فيمكنك ذلك عن طريق استخدام مكتبة Polylang. ونعم يوجد فى أخر دورة تطوير تطبيقات الويب باستخدام لغة PHP فصل عن التعريب وهو شبيه جدا للتوطين ، وفعال اكثر منه. رابط الدورة:https://academy.hsoub.com/learn/php-web-application-development/1 نقطة
-
تحتاجين لتعلم التوطين (localization). التوطين هو عملية تكييف المنتج أو التطبيق ليكون ملائمًا لسوق محدد أو لغة معينة أو ثقافة معينة. يتضمن التوطين تعديل النصوص والرسومات والتصميمات والتوجيهات البرمجية بحيث تتوافق مع الاحتياجات والتفضيلات الثقافية واللغوية للمستخدمين في السوق المستهدف. يشمل التوطين تغيير النصوص، وتعديل التنسيق والتصميم ليناسب الثقافة واللغة المستهدفة، وتحويل العملات والتوقيت إلى النظام المستخدم في البلد المستهدف، وتوفير ترجمة للواجهة والمحتوى، بالإضافة إلى تعديل الأشكال والرموز الخاصة بتوافقها مع الثقافة المستهدفة. على سبيل المثال، إذا كنت تطور تطبيقًا للهواتف الذكية وترغب في إطلاقه في الأسواق العربية، فسيكون عليك توطين التطبيق ليدعم اللغة العربية، وقد يتطلب ذلك ترجمة النصوص إلى العربية، وضبط التصميم ليدعم الكتابة من اليمين إلى اليسار، وتغيير بعض الرموز والصور لتتناسب مع الثقافة العربية، وغيرها من التعديلات المتعلقة باللغة والثقافة. ويمكنكى استخدام لغة ال JavaScript للقيام بهذه المهمة. وهذا مثال بسيط للتوطين بين اربعة لغات مختلفة، اللغة العربية والإنجليزية والفرنسية والإسبانية. function translateText(text, targetLanguage) { // قاموس الترجمة const translations = { "مرحبا": { "en": "Hello", "fr": "Bonjour", "es": "Hola" }, "كيف حالك؟": { "en": "How are you?", "fr": "Comment ça va?", "es": "¿Cómo estás?" }, }; // التحقق مما إذا كان النص موجودًا في قاموس الترجمة if (translations[text]) { // إذا كان النص موجود، فتحقق من توفر الترحمة للغة المستهدفة if (translations[text][targetLanguage]) { // إذا وجدت الترجمة، قم بإرجاعها return translations[text][targetLanguage]; } else { // إذا لم تجد الترجمة، قم بإرجاع النص الأصلي return text; } } else { // إذا لم يكن النص موجودًا في قاموس الترجمة، قم بإرجاع النص الأصلي return text; } }; const originalText = "مرحبا"; const targetLanguage = "en"; // اللغة المستهدفة هنا هي الإنجليزية const translatedText = translateText(originalText, targetLanguage); console.log(translatedText); // Output: Hello1 نقطة
-
الأمر يعتمد على التقنيات المستخدم في الموقع هل يتم استخدام React أم لارافل؟ عامًة في جافاسكريبت هناك مكتبة i18next وذلك هو المستند الرسمي: https://www.i18next.com/ حيث تدعم i18next تحميل الملفات الترجمة بصيغ مختلفة مثل JSON و PO وغيرها، وتستطيع استخدامها في تطبيقات React و Angular و Vue.js وغيرها. وإن كان الموقع يتم تصييره rendering عن طريق SSR أي من جهة الخادم، فهنا الترجمة تتم على الخادم من خلال متغيرات وملف به الترجمات المطلوبة سواء من خلال إطار next.js أو من خلال PHP تفقدي النقاش التالي: أما إن كان الموقع Static تستطيعي إنشاء صفحات منفصلة لكل لغة والتبديل بينها من خلال جافاسكريبت.1 نقطة
-
من متابعتي لردودك فا انت تريد معرفة اي منهم الافضل في استخدامهم في مشروعك وتريد مقارنه بينهم من جميع الجوانب بالنسبة لأي مطور برامج، فإن اختيار قاعدة البيانات المناسبة يعتمد على متطلبات المشروع والسيناريوهات المحتملة التي قد تواجهها. ساحاول توضيح مقارنة مفصلة في بعض الجوانب المهمه بين MySQL و SQLite3: الأداء: MySQL: توفر MySQL أداءً ممتازًا للتطبيقات التي تتطلب معالجة بيانات كبيرة وعمليات متعددة. بما أنه يعمل على خادم مستقل، فإنه يمكنه تحمل بيانات كبيرة بشكل أفضل من SQLite3. SQLite3: بالرغم من أنها قاعدة بيانات مدمجة، إلا أن SQLite3 توفر أداء جيد لتطبيقات الحجم الصغير إلى المتوسط. وبسبب طبيعتها المحلية، فهي تعمل بشكل جيد للتطبيقات التي لا تتطلب موارد كبيرة أو ضغط عالي. سهولة الاستخدام: MySQL: قد تكون عملية تثبيت وتكوين خادم MySQL معقدة بعض الشيء، خاصةً للمبتدئين. ومع ذلك، بمجرد الانتهاء من التثبيت، توفر MySQL واجهة برمجة تطبيقات (API) قوية ومرونة في استخدامها. SQLite3: بسبب طبيعتها المحلية، تكون SQLite3 أسهل في الاستخدام، حيث لا تتطلب التثبيت أو التكوين لخادم خارجي. يمكنك بسهولة إنشاء وإدارة قواعد البيانات SQLite3 داخل تطبيقاتهم بواسطة مكتبة SQLite3 في Python. القابلية للتوسيع: MySQL: يوفر MySQL العديد من الميزات والخيارات للتوسيع وتحسين أدائه، بما في ذلك التكنولوجيا الرأسية والأدوات الإضافية مثل إعدادات الأداء والتجزئة والتكرار الأفقي. SQLite3: تقدم SQLite3 القليل من الخيارات للتوسيع مقارنة بـ MySQL، حيث تكون مناسبة بشكل رئيسي لتطبيقات صغيرة إلى متوسطة الحجم. باختصار، إذا كنت تحتاج إلى قاعدة بيانات متطورة وتستخدم لتطبيقات كبيرة الحجم وتحميل عالي، فقد يكون MySQL الخيار المفضل. أما إذا كنت تبحث عن حل سهل الاستخدام ومناسب لتطبيقات صغيرة إلى متوسطة الحجم، فقد يكون SQLite3 هو الخيار الأفضل لك بعض المقالات التي يمكنك قراتها للتعمق في موضوعك هنا1 نقطة
-
MySQL وSQLite3 يعتبران اثنين من أبرز أنظمة قواعد البيانات في عالم تطوير البرمجيات، وعلى الرغم من أن كلاهما يقدم وظائف مشابهة، إلا أنهما يختلفان في الاستخدامات والمميزات التي يقدمانها. فمثلا نجد أن MySQL تعتبر قاعدة بيانات متقدمة متعددة المستخدمين، مصممة للتطبيقات التي تحتاج إلى معالجة كميات كبيرة من البيانات والتعامل مع العمليات المتعددة بفعالية. ول MySQL إمكانية توزيع البيانات عبر عدة خوادم، مما يجعلها مثالية للتطبيقات ذات الحمولة العالية والتطبيقات التي تتطلب مرونة في التوسع. بالمقابل، SQLite3 تعتبر قاعدة بيانات خفيفة الوزن ومدمجة داخل التطبيق، مما يجعلها سهلة الاستخدام ومناسبة للتطبيقات الصغيرة والمحمولة. بفضل بساطتها وقابليتها للتضمين، تستخدم SQLite3 في تطوير التطبيقات البسيطة التي لا تتطلب مستويات عالية من التعقيد. من حيث الأداء، تتفوق MySQL في معالجة الحمولات الكبيرة والعمليات المتعددة، بينما تُعتبر SQLite3 أكثر فعالية في تخزين البيانات والتعامل مع الحمولات الصغيرة. في النهاية، الاختيار بين MySQL وSQLite3 يعتمد على احتياجات مشروعك ومتطلباته. فمثلا إذا كنت بحاجة إلى قاعدة بيانات متقدمة مع قدرات توزيع البيانات وتكامل متقدم، فإن MySQL هي الخيار الأمثل. أما إذا كنت تبحث عن حل بسيط ومدمج داخل التطبيق، فإن SQLite3 يمكن أن يكون الخيار المثالي والأنسب.1 نقطة
-
1 نقطة
-
MySQL تستخدم في بيئات متطورة حيث يكون هناك حاجة لمعالجة كميات كبيرة من البيانات وتتيح MySQL قدرة توزيع البيانات عبر عدة خوادم ويوفر خيارات لتحسين الأداء والتوسعية الأفقية. بينما نستطيع استخدام SQLite3 مع البيانات الكبيرة، إلا أنك ستواجه قيودًا في الأداء عندما تتعامل مع كميات كبيرة جدًا من البيانات، حيث تكون SQLite3 ممتازة لتطبيقات الويب الصغيرة والتطبيقات المحمولة والتطبيقات التي لا تتطلب مستوى عالٍ من النشاط والمعالجة. وعليك مراعاة هيكلة وتصميم قاعدة البيانات بشكل جيد لضمان الأداء الأمثل بغض النظر عن نوع قاعدة البيانات، ومراجعة كيفية تنظيم الجداول واستخدام الفهارس والاستعلامات بشكل فعال.1 نقطة
-
الإصدار 1.0.0
4346 تنزيل
تُعَد وظائف تحسين المواقع لمحركات البحث أو متخصص سيو SEO واحدةً من أكثر المهن المطلوبة من قبل أصحاب المواقع والمتاجر الإلكترونية في وقتنا الحالي إذ لا نفع من أي موقع أو محتوى منشور على الإنترنت ما لم يصل إلى الجمهور المخصص له وهو مثل أداة أو مكتبة منشأة في مكان ما لا يعرف أحد كيفية الوصول إليها على عظم النفع فيها، ولمَّا كان الاعتماد على محركات البحث اعتمادًا رئيسيًا بل أحيانًا كليًا للبحث عن محتوى والوصول إليه، فكان لزامًا على كل صاحب محتوى أو موقع أو تطبيق ويب موجود على الإنترنت أن يهتم بهذه النقطة اهتمامًا كبيرًا ليتأكد من ظهور محتواه في نتائج البحث بل وحتى المنافسة على أعلى مرتبة عندما يبحث أحد عن المحتوى ذاك أو ما يتعلق به. تحسين محركات البحث Search Engine Optimization ويختصر إلى سيو SEO هو تعبير مجازي شائع إذ في الحقيقة لا يمكن تحسين محركات البحث أو التعديل عليها، بل يُقصد من ذاك التعبير ممارسات وعمليات تجرى بهدف تحسين ظهور موقعك ضمن نتائج محركات البحث في أعلى النتائج، فهل ترى كم الجملة طويل لذا جاء ذلك الاختصار! ودرج حتى استعمال كلمة سيو -تعريب الاختصار الأجنبي SEO- للإشارة إلى ذلك التعبير المختصر أصلًا وهو ما سنستعمله ضمن الكتاب. يُعد هذا الكتاب مدخلًا شاملًا إلى مجال تحسين محركات البحث أو السيو SEO وممارساتها وأدواتها، بهدف تعلم كيف تُحسِّن من ظهور موقعك عبر محركات البحث، إلى جانب زيادة معدل زياراته الشهرية بنسب جيدة، وسيركز الكتاب بالإضافة إلى ذلك على سرد أفضل الممارسات الشائعة طيلة رحلتك في إضافة المحتوى لموقعك وبناءه حتى يواصل تصدره في نتائج البحث فقد لا يكون هنالك ممارسات ثابتة على فترة طويلة من الزمن نتيجة تغير خوارزميات محركات البحث في عرض النتائج التي تحارب باستمرار الخداع والغش لتجنب تصدر نتائج لا تستحق أن تصل إلى صدارة نتائج البحث. عَمِل على الكتاب متخصص سيو -المؤلف علي القاسم- وقد أفاد وأجاد من خبرته الطويلة في هذا المجال كما اعتمدنا على سلسلة The Beginner's Guide to SEO الشهيرة من موقع MOZ والذي يقدم أشهر الأدوات المستعملة في تخصص السيو، فتلك السلسلة معتمدة أيضًا على خبرة عميقة في المجال، ولم نقتصر على ذلك، بل استشرنا في السلسلة فريق التسويق التابع لشركة حسوب وأخذنا منهم نصائح وملاحظات قيمة وأضفناها في الكتاب، وكل ذلك يضفي قيمةً كبيرةً على الكتاب. أخيرًا وليس آخرًا، حاولنا جعل الكتاب دليلًا شاملًا وفي الوقت نفسه مختصرًا فخير الكلام ما قل ودل ليأخذ بيدك إلى تعلم تخصص السيو وتحسين محركات البحث ويضعك على بداية الطريق لتدخل هذا المجال وتحصل على فرصة عمل أو تُحسن من مهاراتك وترتقي بنفسك إن كنت متخصصًا في السيو. هذا الكتاب مرخص بموجب رخصة المشاع الإبداعي Creative Commons «نسب المُصنَّف - غير تجاري - الترخيص بالمثل 4.0». يمكنك قراءة فصول الكتاب على شكل مقالات مجموعة ضمن وسم «تحسين محركات البحث 101» وتجد روابطها تاليًا: تحسين محركات البحث SEO وأهميتها لموقعك الإلكتروني كيفية عمل محركات البحث وترتيب جوجل لصفحات الموقع الإلكتروني اختيار الكلمات المفتاحية المناسبة لموقعك الإلكتروني تهيئة الموقع داخليا لمحركات البحث On Page SEO ما هو السيو التقني Technical Seo وأهميته للموقع الإلكتروني بناء الروابط الخلفية للموقع وتحسين موثوقيته من ناحية السيو مؤشرات أداء السيو: بارامترات قياس تحسين محركات البحث لموقعك وتتبع أدائه أداة مشرفي المواقع من جوجل Google Search Console1 نقطة -
علينا أولًا وقبل أن ندخل في موضوع استخدام TypeScript مع React تحديد ما يلزمنا وما الذي نهدف لتحقيقه. فعندما يعمل كل شيء كما ينبغي، ستساعدنا TS في التقاط الأخطاء التالية: محاولة تمرير خاصيات Props زائدة أو غير مطلوبة لمكوِّن نسيان تمرير خاصية يحتاجها المكوَّن تمرير خاصية من النوع الخاطئ إلى مكوِّن ستساعدنا TS على التقاط أيًا من الأخطاء السابقة ضمن المحرر مباشرة. وفي حال لم نستخدم TS، سنضطر إلى التقاط هذه الأخطاء لاحقًا عن طريق الاختبارات، وربما سنقضي وقتًا مرهقًا في إيجاد مسبباتها. هذه الأسباب كافية حتى الآن لنبدأ إذًا! إنشاء تطبيق React باستخدام TypeScript يمكننا استخدام create-react-app لإنشاء تطبيق React باستخدام TS بإضافة الوسيط template إلى سكربت التهيئة الأولية. لذا نفّذ الأمر التالي لإنشاء تطبيق create-react-app باستخدام TS: npx create-react-app my-app --template typescript ينبغي بعد تنفيذ الأمر، أن تحصل على تطبيق React مكتمل يستخدم TS. يمكنك أن تشغل التطبيق باستخدام الأمر npm start في جذر المشروع. لو ألقينا نظرة على الملفات والمجلدات، ستجد أن التطبيق لا يختلف كثيرًا عن التطبيقات التي تستخدم JavaScript صرفة. إذ تقتصر الاختلافات على تحول الملفات التي تحمل إحدى اللاحقتين js. و jsx. إلى ملفات باللاحقتين ts.وtsx. وستحتوي هذه الملفات على مسجلات للأنواع، كما سيحتوي المجلد الجذري على الملف tsconfig.json. لنلق نظرة الآن على الملف tsconfig.json الذي أُنشئ نيابة عنا. ينبغي أن تكون قواعد التهيئة ضمنه مناسبة إلى حد ما، إلا أنّ هذه القواعد ستسمح بتصريف ملفات JavaScript، لأن القاعدة allowJs تأخذ القيمة "true". لا بأس بذلك إن كنت ستمزج بين TS و JavaScript (في الحالة التي تعمل فيها مثلًا على تحويل شيفرة JS إلى TS أو ما شابه)، لكننا نريد هنا إنشاء تطبيق TS صرف، لذا سنغير قيمة القاعدة allowJs إلى false. استخدمنا في المشروع السابق المدقق لمساعدتنا في فرض أسلوب برمجة معين، سنفعل ذلك أيضًا في هذا التطبيق. لا حاجة لتثبيت أية اعتماديات لأن create-react-app قد اهتم بكل الترتيبات. يحمل الملف ذو اللاحقة "eslintrc." قواعد eslint التالية: { "env": { "browser": true, "es6": true, "jest": true }, "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended" ], "plugins": ["react", "@typescript-eslint"], "settings": { "react": { "pragma": "React", "version": "detect" } }, "rules": { "@typescript-eslint/explicit-function-return-type": 0 } } طالما أنّ النوع الذي تعيده جميع مكوّنات React هي عناصر JSX أو القيمة null، سنغير قواعد المدقق قليلًا بإلغاء تفعيل القاعدة explicit-function-return-type. وهكذا لن نحتاج إلى التصريح عن نوع القيمة التي تعيدها الدالة في كل مكان. علينا أيضًا أن نجعل المدقق قادرًا على فهم ملفات "tsx."، وهي المقابل في TS لملفات "jsx." في React. سننفذ ذلك بتغيير السكربت lint في الملف package.json على النحو التالي: { // ... "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "lint": "eslint './src/**/*.{ts,tsx}'" }, // ... } قد تحتاج إلى استخدام علامة التنصيص المزدوجة عند كتابة مسار المدقق: {lint": "eslint './src/**/*.{ts,tsx} وذلك إن كنت تعمل على نظام Windows. لو نفذنا الآن الأمر npm run lint سنحصل على رسالة خطأ من المدقق eslint مجددًا: لماذا يحدث ذلك؟ تخبرنا رسالة الخطأ أنّ الملف serviceWorker.ts لا يتقيد بقواعد المدقق. والسبب في ذلك، أنّ الدالة register ستستخدم دوال أخرى عُرِّفت لاحقًا في الملف نفسه وهذا لا يتوافق مع القاعدةtypescript-eslint/no-use-before-define@ . ولإصلاح المشكلة لا بدّ من نقل الدالة register إلى آخر الملف. لا ينبغي أن تظهر أية أخطاء بعد الآن. لا يشكل الخطأ السابق عائقًا في واقع الأمر، لأننا لن نحتاج إلى الملف serviceWorker.ts. لذا من الأفضل حذفه. مكونات React مع TypeScript لنتأمل المثال التالي لتطبيق React كُتب باستخدام JavaScript: import React from "react"; import ReactDOM from 'react-dom'; import PropTypes from "prop-types"; const Welcome = props => { return <h1>Hello, {props.name}</h1>; }; Welcome.propTypes = { name: PropTypes.string }; const element = <Welcome name="Sara" />; ReactDOM.render(element, document.getElementById("root")); لدينا في هذا المثال المكوًن Welcome الذي نمرر إليه اسمًا كخاصية ليقوم بتصييره على الشاشة. ينبغي أن يكون الاسم من النوع string وقد استخدمنا الحزمة prop-types التي تعرفنا عليها في القسم 5، لكي نحصل على تلميحات حول الأنواع المطلوبة لخصائص المكوّنات وتحذيرات عند استخدام خصائص من النوع الخاطئ. لن نحتاج إلى الحزمة prop-types أبدًا عند استخدام TS. إذ يمكننا تعريف الأنواع بمساعدة TS، وذلك باستخدام واجهة النوع "FunctionComponent" أو باستخدام اسمها المستعار FC. عندما نستخدم TS مع المكوّنات، ستبدو مسجلات الأنواع مختلفة قليلًا عن شيفرات TS الأخرى. حيث نضيف النوع إلى المتغيًر الذي يُسند إليه المكوّن بدلًا من الدالة وخصائصها. يدعى النوع الناتج عن "FunctionComponent" بالنوع المُعمَّم generic. بحيث يمكن أن نمرر لهذه الواجهة نوعًا كمعامل، ثم تستخدمه على أنه النوع الخاص بها. تبدو تصريحات React.FC وReact.FunctionComponent على النحو التالي: type FC<P = {}> = FunctionComponent<P>; interface FunctionComponent<P = {}> { (props: PropsWithChildren<P>, context?: any): ReactElement | null; propTypes?: WeakValidationMap<P>; contextTypes?: ValidationMap<any>; defaultProps?: Partial<P>; displayName?: string; } سترى أولًا أن FC ببساطة هو اسم مستعار لواجهة النوع FunctionComponent، وكلاهما من النوع المعمم الذي يمكن تمييزه بسهولة من قوسي الزاوية "<>" بعد اسم النوع. سترى داخل قوسي الزاوية هذه الشيفرة {}=P. وتعني أنه بالإمكان تمرير نوع كمعامل. سيُعرَف النوع الذي سيُمرَّر بالاسم P، وهو افتراضيًا كائن فارغ {}. لنلق نظرة على السطر الأول من شيفرة FunctionComponent: (props: PropsWithChildren<P>, context?: any): ReactElement | null; تحمل الخصائص النوع PropsWithChildren، وهو أيضًا نوع معمم يُمرَّر إليه النوع P. يمثل النوع "PropsWithChildren" بدوره تقاطعًا intersection بين النوعين بالشكل التالي: type PropsWithChildren<P> = P | { children?: ReactNode }; كل ما نحتاج إليه الآن من هذا الشرح الذي قد يبدو معقدًا بأنه من الممكن تعريف النوع وتمريره إلى واجهة النوع FunctionComponent، حيث ستحمل خصائصها بعد ذلك النوع الذي عرًفناه بالإضافة إلى المكوّنات الأبناء. لنعد إلى مثالنا ونحاول أن نعرًف نوعًا لخصائص المكوًن Welcome باستخدام TS: interface WelcomeProps { name: string; } const Welcome: React.FC<WelcomeProps> = (props) => { return <h1>Hello, {props.name}</h1>; }; const element = <Welcome name="Sara" />; ReactDOM.render(element, document.getElementById("root")); عرًفنا في الشيفرة السابقة واجهة النوع WelcomeProps ومررناها إلى المكوّن Welcome عندما صرّحنا عن نوعه: const Welcome: React.FC<WelcomeProps>; يمكننا كتابة الشيفرة بأسلوب أقصر: const Welcome: React.FC<{ name: string }> = ({ name }) => ( <h1>Hello, {name}</h1> ); سيعرف المحرر الآن أنّ الخاصية من النوع string. لكن المدقق لن يقتنع تمامًا بما فعلنا، وسيعترض بأن الخاصية name غير موجودة عند تقييم الخصائص. ويحدث هذا لأنّ قاعدة المدقق ستتوقع منا أن نعرِّف أنواعًا لجميع الخصائص. فلن يدرك المدقق أننا نستخدم TS في تعريف الأنواع لخصائصنا. لإصلاح الخلل، لا بدّ من إضافة قاعدة تدقيق جديدة إلى الملف "eslintrc.": { // ... "rules": { "react/prop-types": 0, }, // ... } التمرين 9.14 9.14 أنشئ تطبيق create-react-app مستخدمًا TS وهيئ المدقق لمشروعك بالطريقة التي تعلمناها. يشبه هذا التمرين تمرينًا نفَّذناه في القسم 1 من المنهاج لكن باستخدام TS هذه المرة بالإضافة إلى بعض التعديلات. إبدأ بتعديل محتويات الملف "index.tsx" لتصبح على النحو التالي: import React from "react"; import ReactDOM from "react-dom"; const App: React.FC = () => { const courseName = "Half Stack application development"; const courseParts = [ { name: "Fundamentals", exerciseCount: 10 }, { name: "Using props to pass data", exerciseCount: 7 }, { name: "Deeper type usage", exerciseCount: 14 } ]; return ( <div> <h1>{courseName}</h1> <p> {courseParts[0].name} {courseParts[0].exerciseCount} </p> <p> {courseParts[1].name} {courseParts[1].exerciseCount} </p> <p> {courseParts[2].name} {courseParts[2].exerciseCount} </p> <p> Number of exercises{" "} {courseParts.reduce((carry, part) => carry + part.exerciseCount, 0)} </p> </div> ); }; ReactDOM.render(<App />, document.getElementById("root")); واحذف الملفات غير الضرورية. إنّ التطبيق ككل موجود في مكوّن واحد، وهذا ما لا نريده. أعد كتابة الشيفرة لتتوزع على ثلاثة مكوّنات: Header وContent وTotal. ابق على جميع البيانات ضمن المكوّن App الذي سيمرر كل البيانات اللازمة لمكوّن كخصائص. واحرص على تعريف نوع لكل خاصية من خصائص المكوُنات. سيتحمل المكوًن Header مسؤولية تصيير اسم المنهاج، وسيصيّر المكوّن Content أسماء الأقسام المختلفة وعدد التمارين في كل قسم، أما المكوّن Total فسيصيّر مجموع التمارين في كل الأقسام. سيبدو المكوًن App بالشكل التالي تقريبًا: const App = () => { // const-declarations return ( <div> <Header name={courseName} /> <Content ... /> <Total ... /> </div> ) }; استخدام أدق للأنواع يتضمن المثال السابق منهاجًا من ثلاثة أقسام، ويمتلك كل قسم نفس الصفتين name وexcerciseCount. لكن ماذا لو احتجنا إلى صفات أخرى، وتطلّب كل قسم صفات مختلفة عن الآخر؟ كيف ستبدو شيفرة التطبيق؟ لنتأمل المثال التالي: const courseParts = [ { name: "Fundamentals", exerciseCount: 10, description: "This is an awesome course part" }, { name: "Using props to pass data", exerciseCount: 7, groupProjectCount: 3 }, { name: "Deeper type usage", exerciseCount: 14, description: "Confusing description", exerciseSubmissionLink: "https://fake-exercise-submit.made-up-url.dev" } ]; أضفنا في الشيفرة السابقة صفات أخرى إلى كل قسم. يمتلك الآن كل قسم الصفتين name وexerciseCount، ويمتلك القسمان الأول والثالث الصفة description، كما يمتلك القسمان الثاني والثالث بعض الصفات الإضافية الخاصة. لنتخيل أنّ تطبيقنا سيستمر في النمو، وأننا سنضطر إلى تمرير الأقسام المختلفة ضمن الشيفرة. وقد تّضاف أيضًا خصائص أخرى أو أقسام أخرى. كيف سنضمن أن الشيفرة قادرة على التعامل مع كل الأنواع المختلفة للبيانات بشكل صحيح، وأننا لن ننس تصيير أحد أقسام المنهاج على صفحة ما؟ هنا ستظهر فائدة اللغة TS! لنبدأ بتعريف أنواع لأقسام المنهاج المختلفة: interface CoursePartOne { name: "Fundamentals"; exerciseCount: number; description: string; } interface CoursePartTwo { name: "Using props to pass data"; exerciseCount: number; groupProjectCount: number; } interface CoursePartThree { name: "Deeper type usage"; exerciseCount: number; description: string; exerciseSubmissionLink: string; } ستُنشئ الشيفرة التالية نوعًا موّحَدًا من كل الأنواع. وبالتالي يمكننا استخدامه من أجل مصفوفتنا التي يُفترض بها أن تقبل أي نوع من الأنواع التي تحملها أقسام المنهاج: type CoursePart = CoursePartOne | CoursePartTwo | CoursePartThree; يمكننا الأن تحديد نوع المتغيّر Coursepart، وسيحذرنا المحرر تلقائيًا إن استخدمنا النوع الخاطئ لإحدى الصفات، أو استخدمنا صفة زائدة، أو نسينا أن نحدد صفة متوقعة. اختبر ذلك بوضع علامة تعليق قبل أي صفة لأي قسم. وبفضل القيمة النصية الحرفية التي تحملها السمة name، يمكن أن تحدد TS أي قسم سيحتاج إلى أية صفات إضافية، حتى لو عرفنا المتغير على أنه من النوع الموّحد. لم نصل درجة القناعة بتطبيقنا بعد، فلا زال هناك تكرار كثير للأنواع، ونريد أن نتجنب ذلك. لهذا سنبدأ بتعريف الصفات المشتركة بين جميع الأقسام، ثم سنعرِّف نوعًا أساسيًا (Base Type) يحتويها. سنوسع بعد ذلك النوع الأساسي لإنشاء الأنواع الخاصة بكل قسم: interface CoursePartBase { name: string; exerciseCount: number; } interface CoursePartOne extends CoursePartBase { name: "Fundamentals"; description: string; } interface CoursePartTwo extends CoursePartBase { name: "Using props to pass data"; groupProjectCount: number; } interface CoursePartThree extends CoursePartBase { name: "Deeper type usage"; description: string; exerciseSubmissionLink: string; } كيف سنستخدم هذه الأنواع الآن في مكوًناتنا؟ من الطرق المفيدة في استخدام هذه الأنواع هي في عبارة switch case. فعندما تصرُح علنًا عن نوع المتغير أنه من النوع الموّحد أو استدلت TS على ذلك واحتوى كل نوع موجود ضمن النوع الموًحد صفة معينة، يمكن استخدام ذلك كمعرّفات للأنواع. يمكن بعد ذلك استخدام البنية switch case للتنقل بين الصفات وستحدد TS الصفات الموجودة في كل حالة من حالات البنية switch. ستميّز TS في المثال السابق أن المتغير coursePart من النوع CoursePart. ويمكنها عندها أن تستدل أن المتغير part من أحد الأنواع التالية CoursePartOne أو CoursePartTwo أو CoursePartThree. أما الصفة name فهي مختلفة ومميزة لكل نوع، لذلك من الممكن استخدامها لتمييز الأنواع وستكون TS قادرة على تحديد الصفات الموجودة في كل حالة case من حالات البنية switch case. وهكذا ستعطي خطأً إن حاولت على سبيل المثال أن تستخدم الصفة part.descriptionضمن كتلة الحالة Using props to pass data. كيف سنضيف نوعًا جديدًا؟ من الجيد أن نعرف إن كنا قد أنجزنا مسبقًا آليةً للتعامل مع هذا النوع في شيفرتنا، عند إضافة قسم جديد للمنهاج. فإضافة أي نوع جديد في المثال السابق ستجعله موجودًا في الكتلة default من البنية switch case وبالتالي لن يطبع شيء عن هذا النوع. وقد يكون هذا الأمر مقبولًا في بعض الحالات، كالحالة التي نريد فيها التعامل مع نوع واحد محدد من النوع الموّحد، لكن في أغلب الحالات عليك التعامل مع كل الحالات وبشكل منفصل. يمكن في TS أن نستخدم طريقة تدعى "التحقق الشامل من الأنواع". ومبدأ هذه الطريقة: أنه في حال واجهتنا قيمة غير معروفة النوع، نستدعي دالة تقبل قيمة من النوع never وتعيد قيمة من النوع نفسه. تمثل الشيفرة التالية تطبيقًا مباشرًا لهذا المبدأ: /** * Helper function for exhaustive type checking */ const assertNever = (value: never): never => { throw new Error( `Unhandled discriminated union member: ${JSON.stringify(value)}` ); }; لكن لو أردنا استبدال محتويات الكتلة كالتالي: default: return assertNever(part); ووضع علامة تعليق قبل الكتلة Deeper type usage case block سنرى الخطأ التالي: تنص الرسالة أن معاملًا من النوع CoursePartThree لم يُسند إلى معامل من النوع never. أي أننا نستخدم متغيرًا في مكان ما يفترض به أن يكون من النوع never. وهذا ما يدلنا على وجود مشكلة. لكن بمجرد أن نزيل علامة التعليق التي وضعناها على الكتلة Deeper type usage case block سيختفي الخطأ. التمرين 9.15 9.15 أضف في البداية النوع information إلى الملف index.tsx واستبدل المتغير courseParts بالمتغير الموجود في المثال التالي: // new types interface CoursePartBase { name: string; exerciseCount: number; } interface CoursePartOne extends CoursePartBase { name: "Fundamentals"; description: string; } interface CoursePartTwo extends CoursePartBase { name: "Using props to pass data"; groupProjectCount: number; } interface CoursePartThree extends CoursePartBase { name: "Deeper type usage"; description: string; exerciseSubmissionLink: string; } type CoursePart = CoursePartOne | CoursePartTwo | CoursePartThree; // this is the new coursePart variable const courseParts: CoursePart[] = [ { name: "Fundamentals", exerciseCount: 10, description: "This is an awesome course part" }, { name: "Using props to pass data", exerciseCount: 7, groupProjectCount: 3 }, { name: "Deeper type usage", exerciseCount: 14, description: "Confusing description", exerciseSubmissionLink: "https://fake-exercise-submit.made-up-url.dev" } ]; نعلم الآن أن واجهتي النوع CoursePartThree وCoursePartOne يتقاسمان صفة تدعى description بالإضافة إلى الصفات الأساسية (الموجودة في واجهة النوع الأساسية)، وهذه الصفة من النوع string في كلتا الواجهتين. تقتضي مهمتك الأولى أن تصرِّح عن واجهة نوع جديد تتضمن الصفة description وتوسِّع واجهة النوع CoursePartThree. عدّل الشيفرة بعد ذلك بحيث تصبح قادرًا على إزالة الصفة description من الواجهتين دون حدوث أية أخطاء. أنشئ بعد ذلك المكوّن Part الذي يصيّر كل الصفات من كل نوع ضمن أقسام المنهاج. استخدم آلية تحقق شاملة من الأنواع معتمدًا على بنية switch case. استخدم المكوِّن الجديد ضمن المكوّن Content. أضف في النهاية واجهة نوع لقسم جديد يحوي على الأقل الصفات التالية: name وexerciseCount وdescription. ثم أضف واجهة النوع هذه إلى النوع الموّحد CoursePart وأضف البيانات المتعلقة بالقسم إلى المتغير CourseParts. إن لم تكن قد عدّلت المكوّن Content بشكل صحيح، ستحصل على رسالة خطأ، لأنك لم تضف ما يدعم النوع الخاص بالقسم الرابع. أجر التعديلات المناسبة على المكوّن Content لكي تُصيّر كل صفات القسم الجديد دون أخطاء. ملاحظة حول تعريف أنواع للكائنات لقد استخدمنا واجهات النوع لتعريف أنواع للكائنات مثل DiaryEntry من الفقرة السابقة: interface DiaryEntry { id: number; date: string; weather: Weather; visibility: Visibility; comment?: string; } وCoursePart من هذه الفقرة. interface CoursePartBase { name: string; exerciseCount: number; } لقد كان بمقدورنا تنفيذ ذلك باستخدام نوع بديل type alias. type DiaryEntry = { id: number; date: string; weather: Weather; visibility: Visibility; comment?: string; } يمكنك استخدام الأسلوبين Interface وType لإنشاء نوع في معظم الحالات. لكن هنالك بعض النقاط التي ينبغي الانتباه لها. فلو عرَّفت عدة واجهات نوع لها نفس الاسم ستحصل على واجهة نوع مختلطة، بينما لو عرّفت عدة أنواع لها الاسم ذاته ستحصل على رسالة خطأ مفادها أنّ نوعًا يحمل نفس الاسم قد جرى التصريح عنه مسبقًا. ينصحك توثيق TS باستخدام الواجهات في معظم الحالات. العمل مع شيفرة جاهزة من الأفضل عندما تبدأ العمل على شيفرة جاهزة للمرة الأولى أن تلقي نظرة شاملة على هيكلية المشروع وأسلوب العمل. يمكنك البدء بقراءة الملف README.md الموجود في جذر المستودع. يحتوي هذا الملف عادة على وصف موجز للتطبيق ومتطلباته، وكيف سنجعله يعمل لبدء عملية التطوير. إن لم يكن الملف README متاحًا أو أنّ أحدهم فضّل اختصار الوقت ووضعه كمرجع احتياطي، سيكون من الجيد الاطلاع على الملف package.json. ومن الجيد دائمًا تشغيل التطبيق وتجريبه والتحقق من وظائفه. يمكنك أيضًا تصفح هيكيلة مجلد المشروع لتطلع على وظائفه أو/والمعمارية المستخدمة. لكن هذا الأسلوب لن يفيدك دائمًا، فلربما اختار المطوّر أسلوبًا لم تعهده. ركّزنا في تنظيم المشروع التدريبي الذي سنستخدمه في ما تبقى من هذا القسم على الميزات التي يقدمها. حيث يمكنك الاطلاع على الصفحات التي يعرضها التطبيق، وبعض المكوّنات العامة كالوحدات وحالة التطبيق، وتذكر أن للميزات مجالات مختلفة. فالوحدات هي مكونات مرئية على مستوى واجهة المستخدم بينما تحافظ حالة التطبيق على جميع البيانات تحت الستار لكي تستخدمها بقية المكوّنات. تزوّدك TS بالأنواع التي تخبرك عن شكل بنى البيانات والدوال والمكوّنات والحالة التي ستحصل عليها. يمكنك إلقاء نظرة على الملف types.ts أو أي ملف مشابه كبداية. كما سيساعدك VSCode كثيرًا فمجرد توضيحه للمتغيرات والمعاملات سيمنحك رؤية أفضل للشيفرة. ويعتمد هذا كله بالطبع على طريقة استخدام الأنواع في هذا المشروع. إن احتوى المشروع اختبارات أجزاء أو اختبارت تكامل فسيكون الاطلاع عليها مفيدًا بالتأكيد. فالاختبارات أداة مفيدة جدًا عندما تعيد كتابة الشيفرة أو عند كتابة ميزات جديدة للتطبيق. وتأكد من عدم تخريب أية ميزة موجودة عندما تبدأ بالتلاعب بالشيفرة. سترشدك TS أيضًا بما يتعلق بالمعاملات والقيم المعادة عندما تتغير الشيفرة. إن قراءة الشيفرة مهارة بحد ذاتها. لا تقلق إن لم تستطع فهم الشيفرة في البداية، فقد تحتوي الشيفرات على بعض الحالات غير الواضحة أو قد تحتوي أجزاءً أضيفت هنا وهناك خلال عملية التطوير. فلا يمكننا تصور المشاكل التي عانى منها المطوّر السابق لهذه الشيفرة. ويتطلب فهم تفاصيل الشيفرة بشكل كامل الغوص إلى أعماقها وفهم متطلبات المجال الذي تعمل عليه. فكلما قرأت شيفرات أكثر ستصبح أفضل في فهمها واستخدامها. إقرأ شيفرات أكثر مما تكتبه. الواجهة الأمامية لتطبيق إدارة المرضى لقد حان الوقت لإكمال الواجهة الأمامية للواجهة الخلفية التي بنيناها في التمارين السابقة وقبل أن نتعمق في كتابة الشيفرة، سنشغل الواجهتين معًا. إن سار كل شيء على ما يرام، سترى صفحة ويب تضم قائمة بالمرضى. تحضر الصفحة قائمة المرضى من الواجهة الخلفية وتصيّرها على الشاشة ضمن جدول بسيط. ستجد أيضًا زرًا لإضافة مريض جديد إلى الواجهة الخلفية. وطالما أننا نستخدم بيانات وهمية بدلًا من قواعد البيانات، فلن تخزّن البيانات التي أضفناها عند إغلاق الواجهة الخلفية. وطبعًا تقييم تصميم واجهة المستخدم UI ليس في صالح المصمم، لذلك سنهمل موضوع واجهة المستخدم حاليًا. بعد التأكد من كل الأمور، يمكننا الشروع في دراسة الشيفرة. سنجد معظم النقاط المهمة في المجلد /src. ولمساعدتك هنالك أيضًا ملف جاهز يصف الأنواع الرئيسية التي يستخدمها التطبيق، والتي علينا ان نوسِّعها أو نعيد كتابتها خلال التمارين. يمكننا من حيث المبدأ استخدام الأنواع نفسها في الواجهتين الأمامية والخلفية، لكن الواجهة الأمامية ستحتوي على بنى بيانات مختلفة وستستخدمها في حالات مختلفة، مما يجعل الأنواع ضمنها مختلفة. فللواجهة الأمامية على سبيل المثال حالة للتطبيق، ولربما أردت تخزين البيانات ضمن كائنات، بينما تستخدم الواجهة الخلفية مصفوفة. وقد لا تحتاج الواجهة الأمامية إلى كل حقول كائن البيانات المخزّن في الواجهة الخلفية، كما قد تضيف حقولًا جديدة لاستخدامها في التصيير. ستبدو هيكلية المجلد على النحو التالي تحتوي الواجهة الخلفية حاليًا مكوّنين هما: AddPatientModal وPatientListPage. يحتوي المجلد state على الشيفرة التي تتعامل مع حالة الواجهة الأمامية. وتقتصر وظيفة الشيفرة في هذا المجلد على إبقاء البيانات في مكان واحد وتزويد الواجهة بأفعال بسيطة لتبديل حالة التطبيق. التعامل مع حالة التطبيق لندرس الطرق المتبعة في التعامل مع حالة التطبيق. إذ يبدو أنّ الكثير من الأشياء تجري خلف الستار، وهي مختلفة قليلًا عن الطرق التي استخدمت سابقًا في المنهاج. بُني أسلوب إدارة الحالة باستخدام الخطافين useContext وuseReducer للمكتبة React. وهذا قرار صائب لأنّ التطبيق صغير نوعًا ما ولن نحتاج إلى Redux أو إلى أية مكتبات أخرى لإدارة الحالة. ستجد على الانترنت مواد تعليمة مفيدة عن استخدام هذه المقاربة. تستخدم هذه المقاربة أيضًا الواجهة البرمجية context العائدة للمكتبة React، إذ ينص توثيق الواجهة أنها: فالبيانات المشتركة في حالتنا هي حالة التطبيق ودالة الإيفاد التي تستخدم لإجراء التعديلات على البيانات. ستعمل شيفرتنا بأسلوب يشابه كثيرًا أسلوب Redux في إدارة الحالة والذي خبرناه في القسم 6، لكنها شيفرة أسرع لأنها لا تحتاج أية مكتبات خارجية. يفترض هذا القسم أنك مطلع على طريقة عمل Redux على الأقل، وبعبارة أخرى من المفترض أنك اطلعت على الفصل الأول من القسم 6. تتضمن الواجهة البرمجية context قناة تضم حالة التطبيق ودالة إيفاد لتغيير الحالة. وقد حُدِّد نوع للحالة على النحو التالي: export type State = { patients: { [id: string]: Patient }; }; فالحالة كما تُظهر الشيفرة هي كائن بمفتاح واحد يدعى Patients يمتلك قاموسًا، أو بعبارة أخرى، يقبل كائنًا له مفاتيح من النوع "string" مع كائن Patient كقيمة له. يمكن لقرينة الفهرسة أن تكون من أحد النوعين "string" أو"number" إذ يمكننا الوصول إلى قيم الكائن باستخدام هذين النوعين. وبهذا نجبر الحالة أن تتقيد بالصيغة التي نريد، وتمنع المطورين من استخدامها بشكل غير صحيح. لكن انتبه إلى نقطة مهمة! عندما يُصرّح عن نوع بالطريقة التي اتبعناها مع patients، فلن تمتلك TS أية طريقة لمعرفة إن كان المفتاح الذي تحاول الوصول إليه موجودًا أم لا. فلو حاولنا الوصول إلى بيانات مريض بمعرِّف غير موجود، سيعتقد المصرّف أن القيمة المعادة من النوع Patient، ولن تلق أية أخطاء عند محاولة الوصول إلى خصائص هذا الكائن: const myPatient = state.patients['non-existing-id']; console.log(myPatient.name); // لا أخطاء إذ يعتقد المصرّف أن القيمة المعادة //patient من النوع لإصلاح الخلل، يمكننا أن نعرّف نوع القيم التي تحمل بيانات المريض على أنها من نوعٍ موحّد بين Patient وundefined كالتالي: export type State = { patients: { [id: string]: Patient | undefined }; }; وجراء هذا الحل، سيحذرنا المصرّف بالرسالة التالية: const myPatient = state.patients['non-existing-id']; console.log(myPatient.name); // error, Object is possibly 'undefined' من الجيد دائمًا إنشاء هذا النوع مع بعض الإضافات الأمنية عندما تستخدم مثلًا بيانات من مصادر خارجية أو قيمة أدخلها المستخدم للوصول إلى بيانات ضمن شيفرتك. لكن إن كنت متأكدًا من أن شيفرتك قادرة على التعامل مع البيانات غير الموجودة، فلا مانع أبدًا من استخدام أول حلٍ قدمناه. وعلى الرغم من عدم استخدام هذا الأسلوب في هذا القسم، ينبغي الإشارة إلى أن استخدام كائنات Map سيؤمن لك طريقة أكثر تشددًا في استخدام الأنواع. حيث يمكنك أن تُصرح عن نوع لكلٍ من المفتاح ومحتواه. تعيد دالة الوصول ()getإلى كائنات Map نوعًا موحّدًا يجمع بين النوع المصرّح عنه وundefined، وبالتالي ستطلب TS تلقائيُا إجراء تحقق من البيانات المستخلصة من كائن Map: interface State { patients: Map<string, Patient>; } ... const myPatient = state.patients.get('non-existing-id'); // type for myPatient is now Patient | undefined console.log(myPatient.name); // error, Object is possibly 'undefined' console.log(myPatient?.name); // valid code, but will log 'undefined' تُنفذ التعديلات على الحالة باستخدام دوال الاختزال، تمامًا كما في Redux. عُرِّفت هذه الدوال في الملف reducer.ts بالإضافة إلى النوع Action الذي يبدو على النحو التالي: export type Action = | { type: "SET_PATIENT_LIST"; payload: Patient[]; } | { type: "ADD_PATIENT"; payload: Patient; }; تبدو دالة الاختزال مشابه تمامًا للدوال التي أنشأناها في القسم 6. حيث تغيّر حالة كل نوع من الأفعال: export const reducer = (state: State, action: Action): State => { switch (action.type) { case "SET_PATIENT_LIST": return { ...state, patients: { ...action.payload.reduce( (memo, patient) => ({ ...memo, [patient.id]: patient }), {} ), ...state.patients } }; case "ADD_PATIENT": return { ...state, patients: { ...state.patients, [action.payload.id]: action.payload } }; default: return state; } }; ينحصر الفرق في أنّ الحالة الآن على شكل قاموس (أو كائن) بدلًا من المصفوفة التي استخدمناها في القسم 6. تجري الكثير من الأمور في الملف state.ts والتي تهيئ سياق العمل. ويعتبر الخطاف useReducer الذي يستخدم لإنشاء الحالة، ودالة الإيفاد المكونان الرئيسيان لإنجاز التغييرات على حالة التطبيق، حيث يمرران إلى التابع context.povider: export const StateProvider: React.FC<StateProviderProps> = ({ reducer, children }: StateProviderProps) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StateContext.Provider value={[state, dispatch]}> {children} </StateContext.Provider> ); }; يستخدم التابع السابق في تزويد كل المكوّنات بالحالة ودالة الإيفاد، وذلك بفضل الإعدادات الموجودة في الملف index.ts: import { reducer, StateProvider } from "./state"; ReactDOM.render( <StateProvider reducer={reducer}> <App /> </StateProvider>, document.getElementById('root') ); كما يُعرّف أيضًا الخطاف useStateValue: export const useStateValue = () => useContext(StateContext); وتستعمله أيضًا المكوّنات التي تحتاج إلى الحالة أو دالة الإيفاد لتخزينهما: import { useStateValue } from "../state"; // ... const PatientListPage: React.FC = () => { const [{ patients }, dispatch] = useStateValue(); // ... } لا تقلق إن بدا الأمر مربكًا قليلًا، فبالطبع سيبقى كذلك حتى تدرس توثيق context وطريقة استخدامها في إدارة الحالة. لكن ليس عليك فهم كل ذلك بشكل كامل حتى تحل التمارين. من الشائع جدًا أن لا تفهم بشكل كامل ما تفعله الشيفرة خلف الستار، عندما تبدأ العمل على مشروع جاهز. فإن كان المشروع مبني بهيكلية جيدة يمكنك أن تثق أن أية تعديلات مدروسة جيدًا لن تؤثر على عمل التطبيق، على الرغم من عدم إدراكك لكامل آليات العمل التي تجري داخله. مع الوقت ستفهم الأجزاء التي لم تكن مألوفة بالنسبة لك، لكن ذلك لن يحدث بين يوم وليلة وخاصة عندما تعمل على مشروع ذو شيفرة ضخمة. صفحة قائمة المرضى لنتوجه إلى الملف index.ts الموجود في المجلد PateintListPage لنأخذ بعض الأفكار التي تساعدنا على إحضار البيانات من الواجهة الخلفية وتحديث حالة التطبيق. تستخدم الصفحة خطافًا أنشئ خصيصًا لوضع البيانات في الحالة ودالة الإيفاد لتحديث محتوياتها. فعندما ننشئ قائمة بالمرضى لا نحتاج سوى تفكيك خصائص الكائن patients الموجود في حالة التطبيق: import { useStateValue } from "../state"; const PatientListPage: React.FC = () => { const [{ patients }, dispatch] = useStateValue(); // ... } ونستخدم أيضًا الحالة app التي أنشئت باستخدام الخطاف useState لإدارة حالات إظهار الوحدة أو إخفائها، وكذلك معالجة الأخطاء: const [modalOpen, setModalOpen] = React.useState<boolean>(false); const [error, setError] = React.useState<string | undefined>(); نمرر للخطاف useState نوعًا كمعامل، ثم يُطبَّق على الحالة الفعلية. فالمعامل modalOpen من النوع Boolean والمعامل error من النوع string | undefined. وكلا دالتي الضبط اللتان يعيدهما الخطاف useState سيقبلان معاملات من نوع يماثل النوع الذي يمرر إليها من خلال معامل (يمتلك نوعًا). فالنوع الفعلي للدالة setModalOpen هو ما يلي: <<React.Dispatch<React.SetStateAction<boolean. كما نستخدم الدالتين المساعدتين closeModal وopenModal لقراءة البيانات بشكل أفضل وأكثر ملاءمة: const openModal = (): void => setModalOpen(true); const closeModal = (): void => { setModalOpen(false); setError(undefined); }; تتعلق الأنواع في الواجهة الأمامية بما قمت بتطويره بإنشائه عند تطوير الواجهة الخلفية في الفصل السابق. سيحضر المكوّن App عندما يُثبَّت المرضى مستخدمًا المكتبة axios. ويجدر الانتباه إلى أننا مررنا نوعًا على شكل معامل إلى الدالة axios.get لكي نحدد نوع البيانات التي سنحصل عليها من الاستجابة: React.useEffect(() => { axios.get<void>(`${apiBaseUrl}/ping`); const fetchPatientList = async () => { try { const { data: patients } = await axios.get<Patient[]>( `${apiBaseUrl}/patients` ); dispatch({ type: "SET_PATIENT_LIST", payload: patients }); } catch (e) { console.error(e); } }; fetchPatientList(); }, [dispatch]); تحذير! لن تُقيَّم البيانات عندما نمرر النوع كمعامل إلى المكتبة axios. وهو خطر تمامًا وخاصة إن كنت تستخدم واجهة برمجية خارجية. لتفادي ذلك يمكنك إنشاء دوال تقييم مخصصة تتحمل عبء التقييم وتعيد النوع الصحيح، أو يمكنك استخدام "حاميات النوع". ستجد أيضًا عدة مكتبات تزوّدك بأساليب لتقييم البيانات ضمن أنواع مختلفة من التخطيطات، مثل المكتبة io-ts. لكننا وتوخيًا للبساطة سنثق أننا سنحصل على البيانات بشكلها الصحيح من الواجهة الخلفية. وطالما أن تطبيقنا صغير، سنحدِّث الحالة باستدعاء دالة الإيفاد التي يؤمنها لنا الخطاف useStateValue. وسيساعدنا المصرّف بالتأكد من أننا أوفدنا الأفعال بما يوافق النوع Action الذي يضم قيمة محددة مسبقًا من النوع string وحمولة من البيانات payload. dispatch({ type: "SET_PATIENT_LIST", payload: patients }); التمارين 9.16 - 9.18 سنضيف قريبًا النوع Entry إلى التطبيق والذي يمثّل مُدخلًا على شكل ملخّص بسيط عن المريض. يتكون الملخص من نص وصفي، وتاريخ الإنشاء، ومعلومات تتعلق بالاختصاصي الذي أنشأ الملخص ورمز التشخيص المحتمل. ترتبط شيفرة التشخيص بالرموز ICD-10 التي تُعيدها وصلة التخديم api/diagnosis/. سيكون ما ننفذه بسيطًا، إذ يعطي لكل مريض مصفوفة من المُدخلات. سنُجري بعض التحضيرات، قبل الشروع في العمل. 9.16 تطبيق إدارة المرضى: الخطوة 1 أنشئ وصلة تخديم على العنوان api/patients/:id/ تعيد كل المعلومات عن مريض، بما فيها مصفوفة المُدخلات الخاصة به والتي ستبقى فارغة لجميع المرضى. وسّع حاليًا الأنواع في الواجهة الخلفية على النحو التالي: // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface Entry { } export interface Patient { id: string; name: string; ssn: string; occupation: string; gender: Gender; dateOfBirth: string; entries: Entry[]} export type PublicPatient = Omit<Patient, 'ssn' | 'entries' > ينبغي أن تبدو الاستجابة كالتالي: 9.17 تطبيق إدارة المرضى: الخطوة 2. أنشئ صفحة لإظهار المعلومات الكاملة عن مريض ضمن الواجهة الأمامية. ينبغي أن يكون المستخدم قادرًا على الوصول إلى معلومات المريض بالنقر على اسم المريض مثلًا. أحضر البيانات من وصلة التخديم التي أنشأتها في التمرين السابق. أضف المعلومات التي ستحضرها عن المريض إلى حالة التطبيق. لا تحضر المعلومات إن كانت موجودة مسبقًا في حالة التطبيق، أي في الحالة التي يعرض فيها المستخدم معلومات مريض أكثر من مرة. طالما أننا سنستخدم حالة التطبيق ضمن سياق العمل، عليك أن تُعرّف فعلًا جديدًا لتحديث بيانات مريض محدد. يستخدم التطبيق المكتبة Semantic UI React لإضافة التنسيقات إليه. وهي مكتبة مشابهة كثيرًا للمكتبتين React Bootstrap وMaterialUI اللتان تعاملنا معهما في القسم 7. يمكنك استخدامهما لتنسيق المكوّن الجديد، وهذا أمر يعود إليك، فاهتمامنا ينصب الآن على TS. ويستخدم التطبيق أيضًا المكتبة react router للتحكم بإظهار واجهات العرض على الشاشة. عليك إلقاء نظرة على القسم 7 إن لم تكن متمكنًا من فهم آلية عمل الموجّهات routers. ستبدو النتيجة كالتالي: يظهر جنس المريض باستخدام المكون Icon من المكتبة Semantic UI React. ملاحظة: لتصل إلى المُعرِّف بكتابة عنوان المورد، عليك إعطاء الخطاف useParams معامل من نوع مناسب: const { id } = useParams<{ id: string }>(); 9.18 تطبيق إدارة المرضى: الخطوة 3. سننشئ حاليًا كائنات أفعال action في أي مكان نوفد إليه هذه الأفعال. إذ يمتلك المكوّن App مثلًا دالة الإيفاد التالية: dispatch({ type: "SET_PATIENT_LIST", payload: patientListFromApi }); أعد كتابة الشيفرة مستخدمًا دوال توليد الأفعال المُعرّفة ضمن الملف "reducer.tsx". سيتغير المكوِّن App مثلًا على النحو التالي: import { useStateValue, setPatientList } from "./state"; // ... dispatch(setPatientList(patientListFromApi)); مدخلات كاملة أنجزنا في التمرين 9.12 وصلة تخديم لإحضار تشخيص مريض، لكننا لم نستخدمها بعد. من الجيد توسيع بياناتنا قليلُا بعد أن أصبح لدينا صفحة لعرض معلومات المرضى. لنضف إذًا حقلًا من النوع Entry إلى بيانات المريض، تتضمن مدخلاته الدوائية مع التشخيص المحتمل. لنفصل بنية بيانات المرضى السابقة عن الواجهة الخلفية ونستخدم الشكل الجديد الموسَّع. ملاحظة: إن تنسيق البيانات هذه المرة بالصيغة "ts." وليس بالصيغة ".json". كما أن النوعين Gender وPatient جاهزين مسبقًا، لذلك كل ما عليك الآن هو تصحيح مسار إدراجهما إن اقتضت الحاجة. لننشئ النوع Entry بشكل ملائم بناء على البيانات المتوفرة. لو ألقينا الآن نظرة أقرب على البيانات المتوفرة، سنجد أن المُدخلات مختلفة عن بعضها قليلًا. لاحظ الاختلافات بين أول مدخلين على سبيل المثال: { id: 'd811e46d-70b3-4d90-b090-4535c7cf8fb1', date: '2015-01-02', type: 'Hospital', specialist: 'MD House', diagnosisCodes: ['S62.5'], description: "Healing time appr. 2 weeks. patient doesn't remember how he got the injury.", discharge: { date: '2015-01-16', criteria: 'Thumb has healed.', } } ... { id: 'fcd59fa6-c4b4-4fec-ac4d-df4fe1f85f62', date: '2019-08-05', type: 'OccupationalHealthcare', specialist: 'MD House', employerName: 'HyPD', diagnosisCodes: ['Z57.1', 'Z74.3', 'M51.2'], description: 'Patient mistakenly found himself in a nuclear plant waste site without protection gear. Very minor radiation poisoning. ', sickLeave: { startDate: '2019-08-05', endDate: '2019-08-28' } } سنرى أن بعض الحقول متطابقة لكن المُدخل الأول يمتلك الحقل discharge والثاني يمتلك الحقلين employerName وsickLeave. وبشكل عام تحتوي جميع المدخلات حقول مشتركة وحقول خاصة. وبالنظر إلى الحقل type، سنجد ثلاثة أشكال للمدخلات تشير إلى حاجتنا إلى ثلاثة أنواع منفصلة: OccupationalHealthcare Hospital HealthCheck. تشترك المدخلات بعدة حقول، وبالتالي من المناسب إنشاء واجهة نوع أساسي تدعى "Entry" قابلة للتوسّع بإضافة حقول خاصة لكل نوع. تُعَدّ الحقول الآتية مشتركةً بين كل المدخلات: id description date specialist ويبدو أن الحقل diagnosesCodes موجود فقط في المدخلات من النوعين OccupationalHealthcare وHospital. وطالما أنه لا يستخدم دائمًا حتى في هذين النوعين، فسنفترض أنه حقل اختياري. ويمكن إضافته إلى النوع HealthCheck أيضًا، إذ ليس من الضرورة استخدامه في أي من المدخلات الثلاث. ستبدو واجهة النوع الأساسي كالتالي: interface BaseEntry { id: string; description: string; date: string; specialist: string; diagnosisCodes?: string[]; } لو أردنا تهيئة الواجهة بشكل أفضل، ولعلمنا أن النوع Diagnoses قد عُرِّف مسبقًا في الواجهة الخلفية، من الممكن إذًا الإشارة مباشرة إلى حقل الشيفرة للنوع Diagnoses في حال تغيّر نوعه لسبب ما. سننفذ ذلك على النحو التالي: interface BaseEntry { id: string; description: string; date: string; specialist: string; diagnosisCodes?: Array<Diagnosis['code']>; } وتذكّر أن <Array<Type هي صيغة بديلة للتعليمة []Type. ومن الأفضل والأوضح في حالات كهذه أن نستخدم المصفوفة، لأن استخدام الخيار الآخر سيدفعنا إلى تعريف النوع بالعبارة DiagnosisCode والتي تبدو غريبة بعض الشيء. يمكننا الآن وبعد تعريف النوع الأساسي Entry، أن ننشئ الأنواع Entry الموسّعة التي سنستخدمها فعليًا. وسنبدأ بإنشاء النوع HealthCheckEntry. تحتوي المدخلات من النوع HealthCheck على الحقل HealthCheckRating الذي يأخذ قيمًا صحيحة بين 0 و 3. تعني القيمة 0 أن المريض بصحة جيدة، أما القيمة 3 فتعني أن المريض بحالة حرجة. وهذا ما يجعل استخدام التعداد مثاليًا. وبناء على المعطيات السابقة يمكن تعريف النوع HealthCheckEntry على النحو التالي: export enum HealthCheckRating { "Healthy" = 0, "LowRisk" = 1, "HighRisk" = 2, "CriticalRisk" = 3 } interface HealthCheckEntry extends BaseEntry { type: "HealthCheck"; healthCheckRating: HealthCheckRating; } يبقى علينا الآن إنشاء النوعين OccupationalHealthcareEntry وHospitalEntry ومن ثم نضم الأنواع الثلاثة ونصدّرها كنوع موحّد Entry كالتالي: export type Entry = | HospitalEntry | OccupationalHealthcareEntry | HealthCheckEntry; التمارين 9.19 - 9.22 9.19 تطبيق إدارة المرضى: الخطوة 4 عّرف النوعين بما يتلائم مع البيانات الموجودة. تأكد أن الواجهة الخلفية ستعيد المُدخل المناسب عندما تتوجه لإحضار معلومات مريض محدد. استخدم الأنواع بشكل صحيح في الواجهة الخلفية. لا حاجة الآن لتقييم بيانات كل الحقول في الواجهة الخلفية، ويكفي التحقق أن الحقل type سيحتوي قيمة من النوع الصحيح. 9.20 تطبيق إدارة المرضى: الخطوة 5 وسّع صفحة المريض في الواجهة الأمامية لإظهار قائمة تضم تاريخ ووصف ورمز التشخيص لكل مدخلات المريض. يمكنك استخدام نفس النوع Entry الذي عرّفناه الآن ضمن الواجهة الأمامية. ويكفي في هذه التمارين أن ننقل تعريف الأنواع كما هو من الواجهة الخلفية إلى الأمامية (نسخ/لصق). قد يبدو حلك قريبًا من التالي: 9.21 تطبيق إدارة المرضى: الخطوة 6 أحضر التشخيص ثم أضفه إلى حالة التطبيق من وصلة التخديم api/diagnosis/. استخدم بيانات التشخيص الجديدة لإظهار توصيف رموز التشخيصات للمريض. 9.22 تطبيق إدارة المرضى: الخطوة 7 وسّع قائمة المدخلات في صفحة المريض لتتضمن تفاصيل أكثر باستخدام مكوّن جديد يُظهِر بقية المعلومات الموجودة ضمن مدخلات المريض ومميزًا الأنواع عن بعضها. يمكنك أن تستخدمعلى سبيل المثال المكوّن Icon أو أي مكوًن آخر من مكوّنات المكتبة SemanticUI للحصول على مظهر مناسب لقائمتك. ينبغي تنفيذ عملية تصيير شرطية باستخدام البنية switch case وآلية التحقق الشاملة من الأنواع لكي لا تنس أية حالة. تأمل الشكل التالي: ستبدو قائمة المدخلات قريبة من الشكل التالي: نموذج لإضافة مريض قد يكون التعامل مع النماذج مزعجًا أحيانًا في ، لذلك قررنا استخدام الحزمة Formik لإنشاء نموذج لإضافة مريض في تطبيقنا. فيما سيأتي تقديم بسيط اجتزأناه من توثيق : ستنظم Formik الأشياء بتجميع العمليات السابقة في مكان واحد، وستغدو الاختبارات و إعادة كتابة الشيفرة وتقييم النماذج أمرًا يسيرًا. يمكنك إيجاد شيفرة النموذج في الملف src/AddPatientModal/AddPatientForm.tsx ،كما يمكنك إيجاد الدوال المساعدة لهذا النموذج في الملف src/AddPatientModal/FormField.tsx. لو نظرت إلى الملف AddPatientForm.tsx لوجدت أننا أنشأنا نوعًا لقيم النموذج يدعى PatientFormValues. يمثل هذا النوع نسخة معدلة عن النوع Patient بعد حذف الخاصيتين id و entries. فلا نريد أن يرسل المستخدم المعلومات الموجودة ضمن هاتين الخاصيتين عند إنشاء مريض جديد. فالمعرِّف id ستنشئه الواجهة الخلفية والمدخلات entries لا يمكن إضافتها إلا لمريض موجود مسبقًا. export type PatientFormValues = Omit<Patient, "id" | "entries">; سنصرّح تاليًا عن خصائص المكوّن الخاص بالنموذج: interface Props { onSubmit: (values: PatientFormValues) => void; onCancel: () => void; } يتطلب المكوّن وجود خاصيتين: onSubmit وonCancel. وكلاهما دالة استدعاء لا تعيد قيمًا. ينبغي أن تتلقى الدالة كائنًا من النمط "PatientFormValues" كمعامل لكي تتمكن من معالجة قيم النموذج. بالنظر إلى مكوّن الدالة AddPatientForm، ستجد أننا ربطنا به خصائصه، ومن ثم فككنا onSubmit وonCancel من هذه الخصائص. export const AddPatientForm: React.FC<Props> = ({ onSubmit, onCancel }) => { // ... } قبل أن نكمل، لنلق نظرة على الدوال المساعدة في نموذجنا والموجودة في الملف FormField.tsx. لو تحققت من الأشياء التي يصدرها الملف، ستجد النوع GenderOption ومكوني الدوال SelectField وTextField. لنلق نظرة على مكوِّن الدالة SelectField والأنواع المتعلقة به. أنشأنا أولًا نوعًا معممًا لكل كائن خيارات يتضمن قيمة وتسمية. هذه هي أنواع كائنات الخيارات التي سنسمح بها في نموذجنا ضمن حقل الخيارات. وطالما أن الخيار الوحيد الذي سنسمح به هو جنس المريض سنجعل القيمة value من النوع Gender. export type GenderOption = { value: Gender; label: string; }; أعطينا المتغير genderOptions في الملف AddPatientForm.tsx النوع GenderOption وصرحنا أنه مصفوفة تحتوي على كائنات من النوع GenderOption. const genderOptions: GenderOption[] = [ { value: Gender.Male, label: "Male" }, { value: Gender.Female, label: "Female" }, { value: Gender.Other, label: "Other" } ]; لاحظ إيضًا النوع SelectFieldProps. إذ يقوم بتعريف نوع لخصائص مكوِّن الدالة SelectField. سترى أن الخيارات عبارة عن مصفوفة من النوع GenderOption. type SelectFieldProps = { name: string; label: string; options: GenderOption[]; }; إن عمل مكوِّن الدالة SelectField واضح تمامًا. فهو يصيّر التسمية ويختار عنصرًا، ومن ثم يعطي كل الخيارات المتاحة لهذا العنصر ( قيم هذه الخيارات وتسمياتها). export const SelectField: React.FC<SelectFieldProps> = ({ name, label, options }: SelectFieldProps) => ( <Form.Field> <label>{label}</label> <Field as="select" name={name} className="ui dropdown"> {options.map(option => ( <option key={option.value} value={option.value}> {option.label || option.value} </option> ))} </Field> </Form.Field> ); لننتقل الآن إلى مكوِّن الدالة TextField. يُصيِّر هذا المكوّن الكائن Form.Field من المكتبة SemanticUI إلى تسمية ومكوّن Field من المكتبة Formik. يتلقى هذا المكوًن القيمة name والقيمة placeholder كخصائص. interface TextProps extends FieldProps { label: string; placeholder: string; } export const TextField: React.FC<TextProps> = ({ field, label, placeholder }) => ( <Form.Field> <label>{label}</label> <Field placeholder={placeholder} {...field} /> <div style={{ color:'red' }}> <ErrorMessage name={field.name} /> </div> </Form.Field> ); لاحظ أننا استخدمنا المكوّن ErrorMessage من المكتبة Formik لتصيير رسالة خطأ تتعلق بالقيمة المدخلة إن اقتضى الأمر. ينفذ المكوّن كل ما يلزم خلف الستار، فلا حاجة لكتابة شيفرة خاصة لذلك. من الممكن أيضًا الاحتفاظ برسالة الخطأ ضمن المكوّن باستخدام الخاصيّة form. export const TextField: React.FC<TextProps> = ({ field, label, placeholder, form }) => { console.log(form.errors); // ... } سنعود الآن إلى مكوّن النموذج الفعلي AddPatientForm.tsx. سيصيّر مكون الدالة AddPatientForm مكوّن Formik. سيغلّف هذا المكون بقية المكوّنات ويتطلب خاصيتين initialValues وonSubmit. شيفرة دالة الخصائص واضحة تمامًا. سيتعقب مكوِّن Formik حالة النموذج وسيظهر هذه الحالة بالإضافة إلى عدة توابع قابلة لإعادة الاستخدام ومعالجات الأحداث ضمن النموذج عن طريق الخصائص. نستخدم أيضًا الخاصية الاختيارية valiate التي تقبل دالة تقييم وتعيد كائنًا يتضمن الأخطاء المحتملة. نتحقق في نموذجنا أن الحقول النصية ليست فارغة، لكن بالإمكان أن نضيف بعض المعايير الأخرى للتقييم كالتأكد من صحة رقم الضمان الاجتماعي على سبيل المثال. يمكن لرسائل الخطأ التي تحددها دالة التقييم أن تُعرض ضمن مكوّن ErrorMessage الخاص بكل حقل. لنلق نظرة أولًا على المكوّن بشكله الكامل. ثم سنناقش بعدها أقسامه المختلفة بشيء من التفصيل. interface Props { onSubmit: (values: PatientFormValues) => void; onCancel: () => void; } export const AddPatientForm: React.FC<Props> = ({ onSubmit, onCancel }) => { return ( <Formik initialValues={{ name: "", ssn: "", dateOfBirth: "", occupation: "", gender: Gender.Other }} onSubmit={onSubmit} validate={values => { const requiredError = "Field is required"; const errors: { [field: string]: string } = {}; if (!values.name) { errors.name = requiredError; } if (!values.ssn) { errors.ssn = requiredError; } if (!values.dateOfBirth) { errors.dateOfBirth = requiredError; } if (!values.occupation) { errors.occupation = requiredError; } return errors; }} > {({ isValid, dirty }) => { return ( <Form className="form ui"> <Field label="Name" placeholder="Name" name="name" component={TextField} /> <Field label="Social Security Number" placeholder="SSN" name="ssn" component={TextField} /> <Field label="Date Of Birth" placeholder="YYYY-MM-DD" name="dateOfBirth" component={TextField} /> <Field label="Occupation" placeholder="Occupation" name="occupation" component={TextField} /> <SelectField label="Gender" name="gender" options={genderOptions} /> <Grid> <Grid.Column floated="left" width={5}> <Button type="button" onClick={onCancel} color="red"> Cancel </Button> </Grid.Column> <Grid.Column floated="right" width={5}> <Button type="submit" floated="right" color="green" disabled={!dirty || !isValid} > Add </Button> </Grid.Column> </Grid> </Form> ); }} </Formik> ); }; export default AddPatientForm; يمتلك مكوّن التغليف دالة تعيد محتويات النموذج كمكوِّن ابن child component. ونستخدم العنصر Form من المكتبة Formik لتصيير العناصر الفعلية للنموذج. سنضع داخل هذا العنصر مكونات الدوال TextField وSelectField التي أنشأناها في الملف FormField.tsx. سننشئ في النهاية زرين: الأول لإلغاء إرسال معلومات النموذج، وآخر لإرسالها. يستدعي الزر الأول الدالة onCancel مباشرة، ويحرّض الزر الثاني الحدث onSubmitt للمكتبة Formik الذي يستدعي بدوره الدالة onSubmitt من خصائص المكوّن. سيُفعَّل زر الإرسال إن كان النموذج صالحًا (يعيد القيمة valid) و مستعملًا (يعيد القيمة dirty). أي عندما يضيف المستخدم معلومات ضمن بعض الحقول. نعالج عملية إرسال بيانات النموذج باستخدام المكتبة لأنها تسمح لنا باستدعاء دالة التقييم قبل الشروع فعليًا بإرسال البيانات. فإن أعادة دالة التقييم أية أخطاء، سيلغى الإرسال. وُضع الزرين داخل العنصر Grid من المكتبة Formik لضبطهما متجاورين بطريقة سهلة. <Grid> <Grid.Column floated="left" width={5}> <Button type="button" onClick={onCancel} color="red"> Cancel </Button> </Grid.Column> <Grid.Column floated="right" width={5}> <Button type="submit" floated="right" color="green"> Add </Button> </Grid.Column> </Grid> يُمرر الاستدعاء onSubmit من صفحة قائمة المرضى نزولًا إلى نموذج إضافة مريض، ليرسل طلب HTTP-POST إلى الواجهة الخلفية، ويضيف المريض الذي تعيده الواجهة الخلفية إلى حالة التطبيق ويغلق النموذج. إن أعادت الواجهة الخلفية خطأً، سيُعرض الخطأ على النموذج. تمثل الشيفرة التالية دالة الاستدعاء: const submitNewPatient = async (values: FormValues) => { try { const { data: newPatient } = await axios.post<Patient>( `${apiBaseUrl}/patients`, values ); dispatch({ type: "ADD_PATIENT", payload: newPatient }); closeModal(); } catch (e) { console.error(e.response.data); setError(e.response.data.error); } }; ينبغي أن تكون قادرًا على إكمال بقية التمارين في هذا القسم بناء على المعلومات التي قدمناها. وإن راودتك أية شكوك، أعد قراءة الشيفرة الموجودة، فقد تعطيك تلميحًا لكيفية المتابعة. التمارين 9.23 - 9.27 9.23 تطبيق إدارة المرضى: الخطوة 8 صممنا التطبيق بحيث يمكن أن تجد عدة مدخلات لنفس المريض. لا توجد حتى الآن آلية في تطبيقنا لإضافة مُدخلات. عليك الآن أن تضيف وصلة تخديم على العنوان api/patients/:id/entries/ في الواجهة الخلفية، لتتمكن من إضافة مُدخل لمريض. تذكر أن هناك أنواع مختلفة من المدخلات في تطبيقنا، ويجب أن تدعم الواجهة الخلفية كل هذه الأنواع، وتحقق من وجود كل الحقول المطلوبة لكل نوع. 9.24 تطبيق إدارة المرضى: الخطوة 9 ستدعم الواجهة الخلفية الآن إضافة المُدخلات، ونريد أن ننفذ الوظيفة نفسها في الواجهة الأمامية. عليك إذًا إنشاء نموذج لإضافة مُدخل إلى سجل مريض. وتعتبر صفحة المريض مكانًا مناسبًا للوصول إلى هذا النموذج. يكفي في هذا التمرين أن تدعم نوعًا واحدًا من المدخلات، وليس مطلوبًا معالجة كافة الأخطاء الآن. إذ يكفي أن تنشئ بنجاح مدخلًا جديدًا عندما تُملأ حقول النموذج ببيانات صحيحة. ينبغي أن يُضاف مُدخل جديد إلى سجل المريض المحدد بعد نجاح عملية إرسال بيانات النموذج. كما ينبغي تحديث صفحة المريض ليظهر المُدخل الجديد. يمكنك إن أردت استخدام جزء من شيفرة نموذج إضافة مريض، لكن ذلك غير ضروري. انتبه إلى وجود المكوّن الجاهز GiagnosesSelection في الملف FormField.tsx حيث يمكنك استخدامه لضبط الحقل diagnoses بالشكل التالي: const AddEntryForm: React.FC<Props> = ({ onSubmit, onCancel }) => { const [{ diagnoses }] = useStateValue() return ( <Formik initialValues={{ /// ... }} onSubmit={onSubmit} validate={values => { /// ... }} > {({ isValid, dirty, setFieldValue, setFieldTouched }) => { return ( <Form className="form ui"> // ... <DiagnosisSelection setFieldValue={setFieldValue} setFieldTouched={setFieldTouched} diagnoses={Object.values(diagnoses)} /> // ... </Form> ); }} </Formik> ); }; كما يوجد أيضًا مكوّن جاهز يدعى NumberField للقيم العددية ضمن مجال محدد: <Field label="healthCheckRating" name="healthCheckRating" component={NumberField} min={0} max={3} /> 9.25 تطبيق إدارة المرضى: الخطوة 10 وسّع التطبيق ليُظهر رسالة خطأ إن كانت إحدى القيم المطلوبة مفقودة أو بتنسيق غير صحيح. 9.26 تطبيق إدارة المرضى: الخطوة 11 وسّع تطبيقك ليدعم نوعين من المُدخلات، ويُظهر رسالة خطأ إن كانت إحدى القيم المطلوبة مفقودة أو بتنسيق غير صحيح. لا تهتم بالأخطاء المحتملة التي قد تحملها استجابة الخادم. تقتضي الطريقة الأسهل -وبالطبع ليست الأكثر أناقة- لتنفيذ التمرين أن تنشئ نموذجًا منفصلًا لكل مدخل، فقد تواجهك بعض الصعوبات في التعامل مع الأنواع إن استخدمت نمودجًا واحدًا. 9.27 تطبيق إدارة المرضى: الخطوة 12 وسّع التطبيق ليدعم كل أنواع المُدخلات، ويُظهر رسالة خطأ إن كانت إحدى القيم المطلوبة مفقودة أو بتنسيق غير صحيح. لا تهتم بالأخطاء المحتملة التي قد تحملها استجابة الخادم. وهكذا نكون قد وصلنا إلى التمرين الأخير في هذا الفصل وحان الوقت لتسليم الحلول إلى GitHub، والإشارة إلى التمارين التي أنجزتها في منظومة تسليم التمارين. ترجمة -وبتصرف- للفصل React with Types من سلسلة Deep Dive Into Modern Web Development1 نقطة
-
عندما تضع الشركة خطة تسويقٍ استراتيجيةً، فإن السؤال الذي يطرح نفسه هو: "هل ترفع هذه الخطة الاستراتيجية من المستوى العام للشركة، وهل تقدّم اتجاهات استراتيجية جديدة من أجل المستقبل؟" إن الخطة الاستراتيجية الجيّدة، هي تلك التي تساعد الشركة على إدراك العلاقات بين القوى المختلفة في بيئتها المحيطة، مع العلم، أنه يجب على أي شركة أن تضع هذه العلاقات في الحسبان، حتى تكون قادرة على تنفيذ رؤيتها. يركّز التخطيط في معظم الأحيان على الظروف الداخلية، فترى المسؤولين عن وضع الخطة يطرحون أسئلة مثل: "ما هي نقاط القوة والضعف لدينا؟" و"ما هي الأفضلية النسبية التي نتمتع بها؟" و"ما هي مزايا المنتجات التي تميزنا عن غيرنا؟" أما التخطيط الخارجي، فهو يطرح الأسئلة ذاتها، ولكنه يحاول -أيضًا- فهم كيفية ترابط جميع العناصر مع بعضها داخل السوق، وفي هذا الفصل، سوف نسلط الضوء على العوامل الخارجية التي تؤثر على عمل الشركة بصفة عامة، وعلى عملية التسويق بصفة خاصة. وكما يظهر في الشكل التالي، يواجه مديرو التسويق العديد من العوامل الخارجية المحيطة، مثل التقنيات، والزبائن، والمنافسين، والاتجاهات الاجتماعية، والجغرافية السكانية (الديموغرافيا)، بالإضافة إلى الاعتبارات الأخلاقية، والقانونية، والاقتصادية، والسياسية الأخرى. العوامل البيئية المؤثرة على الشركة يُعد أسلوب المسح البيئي أحد الأساليب التي تستعملها الشركات لمراقبة بيئتها المحيطة، ويتضمن هذا الأسلوب عددًا من الأنشطة التي تستهدف جمع المعلومات حول الأحداث، والتوجهات، التي تقع خارج الشركة، ولكنها قد تؤثر على قراراتها، ومن هذه الناحية، يمكن القول: إن عملية المسح البيئي هي بمثابة نظام الإنذار المبكر، الذي يتيح للمسوقين فهم البيئة الحالية، وتوقّع التوجهات المستقبلية. وتستطيع الشركة في إطار بحوث التسويق أن تضع نظامًا معلوماتيًا، لتنظيم جهود المسح البيئي على نحو يتيح الوصول إلى المعلومات المطلوبة، وتوظيفها بسهولة. تتطلب الخطة الاستراتيجية الجيّدة مراقبة البيئة الخارجية المحيطة بالشركة، فهي تمثل مصدر التهديدات، والفرص، وهو ما يوجب على الشركة وضع استراتيجية لاكتشاف التهديدات، والفرص في وقت مبكر، وتخصيص الموارد، والقدرات اللازمة للتعامل معها. أضف إلى ذلك، أن الشركة قد تواجه عددًا كبيرًا من الفرص، والتهديدات المحتملة، وبالتالي يجب أن يكون المسوقون قادرين على تحديد الأولويات، بحسب تأثير هذه الفرص، والتهديدات على الشركة، والحاجة للتعامل معها بسرعة، وكذلك تكلفة الاستراتيجيات اللازمة لمعالجتها، وفي ظل الكم الهائل من المعلومات المتاحة، يجب أن يكون لدى الشركات آلية فعالة لتصنيف المعلومات حسب مدى ارتباطها بالشركة. وباختصار: إن فهم البيئة التسويقية فهمًا جيّدًا، هو السبيل الوحيد لاكتشاف التوجهات في السوق، ومعرفة إن كانت تمثل فرصًا أم تهديدات. العوامل الخارجية المؤثرة على التسويق قد يقع في السوق العديد من التغيرات الخارجة عن إرادة المسوّقين، ومع ذلك فهي تؤثر على عملهم، وفي مواجهة هذه التقلبات، فإن المسوّق الناجح هو من يتعرّف على التغيرات، ويتكيّف معها. تتكون البيئة الخارجية من عدد من العوامل، وفي هذا الفصل سوف نستعرض جميع هذه العوامل، ونناقش باختصار تأثير كل منها على استراتيجية التسويق. المفاجآت الخارجية كارول وولفي، وجين بارنز صديقتان حميمتان، ويربط بينهما حب الحياكة، والرغبة بامتلاك عملهما الخاص، فبعد عامين من الحياكة، تمكنت السيدتان من صناعة حامل للأطفال، وقد شعرتا بأنه قد يكون جذابًا للأمهات اللواتي يرغبن بحمل أطفالهن بطريقة آمنة، ومريحة. لقد أدركت كل من وولفي، وبارنز أنهما بحاجة إلى كثير من المساعدة للنهوض بهذا العمل، ولكنهما لم تتوقعا أبدًا مدى صعوبة الأمر. تواصلت السيدتان مع رئيس قسم التسويق في إحدى الكليات المحليّة، وقد أخبرهما بأنهما سوف تكونان مشروع الطلاب في مساق التسويق المتقدّم، وبعد شهر واحد، حصلت السيدتان على التقرير الأولي، الذي بدأ باستعراض جميع الوكالات، الوسطاء، الذين قد تحتاجان إلى التواصل معهم لبدء العمل، وقد تضمنت القائمة كلًّا من: محامٍ شخصي، ومحامي براءات اختراع، ومحاسب، ومصرف تجاري، وموردي المواد الخام (مثل القماش، والخيوط والدبابيس)، وموزعين (تجار جملة، وتجزئة)، ووكالة إعلانات، وشركة متخصصة في بحوث التسويق، ومخزن، كذلك توجب على السيدتين فهم القدرات، والخيارات المتاحة لدى كل وكالة وكل وسيط، والتكاليف المرتبطة بكل منها. وبما أنهما تعيشان في مدينة صغيرة نسبيًا (حوالي 185,000 نسمة) فلم يكن العديد من هؤلاء الوسطاء موجودين في المدينة، ولذلك عمل المحامي على ربطهما بمحامي براءات اختراع، وشركة متخصصة في بحوث التسويق من مدينة مجاورة، وقد بلغت تكلفة بحث براءة الاختراع 5,500 دولار أمريكي، بينما وصلت تكلفة البحث الأولي إلى 9,300 دولار أمريكي، بينما لم تتجاوز مدخرات وولفي وبارنز 18,000 دولار فقط، ومن الواضح أن السيدتين كانتا تعانيان من نقص التمويل، لذلك توجهتا إلى المصرف، والذي طلب بدوره قائمة أخرى من المتطلبات، حتى تتمكنا من الحصول على القرض، وقد كان من بين هذه المتطلبات: تقديم خطة عمل، وبيانات مالية تقديرية، ونحو ذلك. كذلك أشارت خطة العمل الأولية التي وضعها الطلاب، إلى وجود العديد من المنافسين، الذين يبيعون منتجات مشابهة كثيرًا، للحامل الذي صنعته وولفي وبارنز، أضف إلى ذلك أن مصادر القماش كانت محدودة، وأن الحد الأدنى للشراء هو 500 لفة قماش، أخيرًا، فقد كان معظم تجار التجزئة يبيعون منتجات مشابهة لصالح مصانع أخرى، ولذلك كان من المستبعد أن تعثر وولفي وبارنز على موزعين، وبالمحصلة، فقد أشارت التقديرات إلى أن تكلفة إنتاج وتسويق 30,000 وحدة في العام الأول تصل إلى 1.4 مليون دولار، بهامش ربح أقصى يُقدر بـ 146,000 دولار، وبالتالي، تخلت كل من وولفي وبارنز عن فكرتهما. وفي حين أن هذه القصة قد تبدو حزينة قليلًا، لكنها ليست غريبة، وهي تعكس أهميّة التعرّف على جميع الوسطاء، والوكلاء، الذين يجب على الشركة التعامل معهم، وخلال هذا الكتاب، سوف نستعرض جميع هذه الكيانات الخارجية، ونحاول تقييم أثرها على التسويق في الشركة. المنافسون على غرار العوامل الخارجية الأخرى، يجب على الإدارة أن تصنف العوامل المؤثرة على المنافسة حسب أهميتها، وذلك حتى تتمكن من تطوير الميزة التنافسية، والحفاظ عليها. يركز التحليل التنافسي على الفرص، والتهديدات، التي قد تظهر نتيجة للتغيرات التنافسية الحالية، أو المحتملة، ويُعد تحديد المنافسين الحاليين، والمحتملين، أولى خطوات التحليل التنافسي، فعلى سبيل المثال: لو سألنا أنفسنا: من هم منافسو شركة جنرال موتورز؟ فسوف تضم الإجابة: شركات مثل تويوتا، وفورد، وكرايسلر، وهوندا، ولكن هذه الإجابة ليست سوى البداية. يوضح الجدول رقم 3 منافسي شركة جنرال موتورز، كما يوضح الجدول رقم 4 منافسي شركة نينتندو لألعاب الفيديو. إن من الضروري أن يبدأ المسوّق هذا التحليل بطرح السؤال الآتي: "ما هي المعايير التي يمكن استخدامها للتعرّف على أبرز المنافسين؟" الجدول رقم 3: تحليل منافسي شركة جنرال موتورز: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } وسائل النقل السوق البديلة السيارات أخرى التصليح والصيانة قطع الغيار تويوتا شركة شوين للدراجات تجار السيارات بيب بويز فورد خطوط دلتا الجوية شركة سيرز الرابطة الوطنية لقطع غيار السيارات NAPA كرايسلر الخطوط الجوية الأمريكية شركة كي مارت هوندا هوندا للدراجات النارية محلات التصليح المحلية أودي وسائل النقل العام الجدول رقم 4: تحليل منافسي شركة نينتندو: ألعاب الفيديو وسائل الترفيه الأخرى شركات الألعاب محلات الألعاب في المنزل خارج المنزل الهوايات سيجا The Tilt قضاء الوقت مع العائلة Plitt Theatres لدور السينما صيد الحيوانات، وصيد السمك. أتاري صالات ألعاب الفيديو شركة الإخوة باركر للألعاب فريق نيويورك ميتس للبيسبول الجولف Genesis Mazzio's بلوك باستر لتأجير الأفلام مدن سيكس فلاغز الترفيهية البيسبول، والكشافة يتضح من المثالين السابقين، أن عملية التعرّف على المنافسين قد تكون أوسع بكثير مما تبدو عليه، وأن تضييق دائرة المنافسين بصورة كبيرة، يزيد من احتمالية ظهور منافس غير معروف، ليستولي على حصة من السوق دون سابق إنذار، ومن الواضح أن شركة جنرال موتورز مثلًا، تنافس كلًا من فورد، وكرايسلر وتويوتا وغيرها من شركات صناعة السيارات، ولكنها تنافس -أيضًا- شركة سيرز في سوق التصليح، والصيانة، وتنافس محطات القطارات في المدن الكبرى، وتنافس خطوط الطيران في الرحلات الطويلة، وتنافس شركة شوين، على الأشخاص الذين يركبون الدراجات الهوائية، أما شركة نينتندو، فهي تنافس شركة سيجا في سوق ألعاب الفيديو، ولكنها تنافس -أيضًا- بلوك باستر لتأجير الفيديو، وتنافس صالات الألعاب الرياضية، وتنافس ألعاب الطاولة، وتنافس دور السينما، وحفلات الموسيقى. باختصار، إن المنافسة تدور حول إشباع الحاجات، والرغبات، وليس حول المنتجات، فشركة جنرال موتورز تسعى إلى إشباع الحاجة إلى المواصلات، بينما تسعى شركة نيتندو إلى إشباع الحاجة إلى الترفيه. وإلى جانب التعرّف على المنافسين من وجهة نظر المستهلك، يمكن استخدام معايير أخرى مثل: الموقع الجغرافي للمنافسين، وحجمهم، وتاريخهم، وقنوات التوزيع، وأساليب التسويق لديهم. كذلك يتوجب على المسوّق أن يطرح سؤالًا آخر، وهو: "ما هي المعايير التي نحتاج إليها، لنعرف إن كنا قد تعرفنا على المنافسين بصورة صحيحة؟" بعد التعرّف على المنافسين بصورة صحيحة، قد يكون من المفيد إجراء تقييم لهم، وفق عدد من العوامل الأخرى، المرتبطة بالمنافسة، مثل: الدخول إلى السوق، والقوة التفاوضية للمشترين، والموردين، وحالة المنافسة، وإمكانية طرح منتجات بديلة، وجميع هذه العوامل يرتبط بالمزيج التسويقي لدى الشركة، ويمكن استعمالها لإيجاد عائق يحول دون دخول المنافسين إلى السوق، أو زيادة الوعي بالعلامة التجارية، أو تصعيد النزاع على الحصة السوقية. وتمثل عوائق الدخول ممارسات تجارية تهدف إلى منع الشركات الجديدة، أو القائمة، من الدخول إلى السوق، وكما مرّ معنا -سابقًا- فقد واجهت كارول وولفي، وجين بارنز العديد من العوائق، التي حالت دون دخولهما إلى السوق، وتتعلق عوائق الدخول إلى السوق -عادةً- برأس المال، أو مصروفات الإعلان، أو طبيعة المنتج، أو قنوات التوزيع، أو تكاليف الموردين، وفي هذا السياق فإن اليابان متهمة بانتهاج سياسة غير رسمية، تهدف إلى وضع عوائق ثقافية، تحول دون دخول الشركات الأجنبية إلى السوق الياباني. تلعب القوة التفاوضية للشركات، والموردين، دورًا كبيرًا في بعض المجالات، مثل: الحديد، والسيارات، والحواسيب، وتتمتع الشركات المشترية بالقوة التفاوضية عندما يوجد عدد قليل منها في السوق، أو عندما تكون تكاليف استبدال الموردين منخفضة، أو عندما يمثل المنتج جزءًا كبيرًا من إجمالي التكاليف لدى الشركة، وتُعد هذه الحالة شائعة في شركات التجزئة الكبيرة، مثل: ولمارت، وهوم ديبوت، وفي المقابل، تزداد قوة المورد عندما يكون المنتج مهمًا للشركة المشترية، أو ترتفع تكاليف استبدال المورد، ومن أمثلة هذه الحالة شركتا مايكروسوفت، وبي إم دبليو. كذلك يؤثر المنافسون الحاليون، والمحتملون -بدرجة كبيرة- على آليات المنافسة في السوق، فعلى سبيل المثال: تشتد المنافسة في الأسواق بطيئة النمو، للحصول على أي زيادة مهما كانت قليلة في الحصة السوقية. كذلك تزيد التكاليف الثابتة، والمرتفعة، من ضغط المنافسة على الشركات، وتدفعها إلى استغلال طاقتها الإنتاجية بالكامل، فعلى سبيل المثال: تزيد المستشفيات من إعلاناتها في محاولة لملء أسرتها التي تمثل تكلفة ثابتة، ومرتفعة. اجذب عملاء مستهدفين وحقق أهدافك التسويقية استعن بأفضل خدمات وحلول التسويق الرقمي من خمسات وحقق مبيعات استثنائية اطلب خدمتك الآن العوامل القانونية والأخلاقية المؤثرة على التسويق تتأثر جميع أنشطة الشركة بعوامل قانونية، وأخلاقية، تحدد قواعد العمل، وتهدف القوانين، والسياسات، والأعراف، إلى التأكد من التزام المسوّقين بالتنافس القانوني، والأخلاقي في سعيهم لتوفير خدمات، ومنتجات قادرة على إشباع رغبات الزبائن، وحاجاتهم، ولذلك يجب على المسوّقين الإلمام بقوانين البلد الذي يعملون فيه، والالتزام بها، وعلى سبيل المثال: يجب على المسوّقين في الولايات المتحدة أن يكونوا على معرفة بالأمور القانونية التالية: السياسة المالية: تتأثر القرارات التسويقية بعوامل مالية، مثل: قانون الضرائب، والمخزون النقدي، ومستوى الإنفاق الحكومي، وعلى سبيل المثال: إذا كان الكونجرس في الولايات المتحدة الأمريكيّة يخضع لسيطرة الجمهوريين، فسوف يكون أكثر ميلًا للإنفاق على المعدّات العسكرية، من الإنفاق على البيئة. التشريعات الفدرالية: تسعى التشريعات الفيدرالية إلى التأكد من التزام جهود التسويق بالمصداقية، والمنافسة الشريفة، والتسعير العادل. وعلى سبيل المثال: تؤثر تشريعات محاربة التدخين على صناعة السجائر، والصناعات المرتبطة بها. العلاقة بين الحكومة، والصناعات المختلفة: تحظى بعض القطاعات مثل: الزراعة، والسكك الحديدية، وبناء السفن، وغيرها من القطاعات الأخرى بدعم الحكومة، بينما في المقابل، تتأثر قطاعات أخرى بالضرائب الجمركية المفروضة على الاستيراد، كما هو الحال في قطاع السيارات مثلًا، كذلك قد تخضع بعض القطاعات مثل: (القطارات، والنقل، والخطوط الجوية) لسيطرة الحكومة، وقد كان لتخلي الحكومة الأمريكية عن السيطرة على قطاع الخدمات، تأثير سلبي هائل على خدمات الكهرباء في كاليفورنيا في عام 2001. التشريعات الاجتماعية: تتأثر جهود المسوّقين بمجموعة واسعة من التشريعات ذات الطابع الاجتماعي، مثل: قوانين الحقوق المدنية، وبرامج الحد من البطالة، وقوانين الحفاظ على البيئة، وقد أنفقت صناعة معالجة اللحوم مليارات الدولارات من أجل الالتزام بقوانين منع تلويث المياه. قوانين الولايات: تؤثر قوانين الولايات على عمل المسوّقين من نواحٍ عديدة على سبيل المثال: لا تستطيع شركات الخدمات في ولاية أوريغون، إنفاق أكثر من نصف الدخل الإجمالي على الإعلانات، أما ولاية كاليفورنيا، فقد سنّت قانونًا لتقليل استهلاك الطاقة بواسطة الثلاجات، ومكيّفات الهواء، وفي ولاية نيو جيرسي، دفعت تسع شركات متخصصة في إنتاج الألبان أكثر من 9 مليون دولار لتسوية دعوة قضائية تستهدف تحديد الأسعار. المؤسسات القانونية: تتابع المؤسسات القانونية التابعة للحكومة باستمرار، الأنشطة التسويقية، وانتهاكاتها للقانون، ومن أمثلة هذه المؤسسات: مكتب النائب العام للولايات المتحدة، والغرفة التجارية الفيدرالية، ودائرة سلامة المنتجات الاستهلاكيّة. في الحقيقة لا يوجد جانب من جوانب عمل الشركات إلا ويتأثر بقانون واحد أو أكثر، لذا فمن المستحيل أن نناقش جميع هذه القوانين في ظل المساحة المحدودة لدينا، ولكننا سوف نتطرق باختصار إلى أبرز ثلاثة مجالات قانونية تؤثر على مجال التسويق، وهي: مسؤولية المنتج، ورفع القيود الحكومية، وحماية المستهلك. مسؤولية المنتج لقد أصبحت المحاكم تحمّل البائعين بصورة متزايدة المسؤولية عن أمان منتجاتهم، وبصفة عامّة تحمّل المحاكم في الولايات المتحدة المُنتِجين المسؤولية الكاملة عن أي عيب في المنتَج، يتسبب بإصابات خلال استخدامه الطبيعي، كذلك قد يتحمّل المُنتجون المسؤولية إن كان تصميم المنتج، أو بناؤه، أو تعليمات تشغيله، أو تحذيرات الأمان، تجعل منه منتجًا خطير الاستخدام. رجلان من ولاية ماريلاند الأمريكية، قررا استخدام مجفف الغسيل لتجفيف منطاد، فانفجر المجفف، وتسبب بإصابتهما، فرفعا دعوة قضائية ضد الشركة وفازا بها. طفل عمره عامان، يُعالج من تشنج الشعب الهوائية، لكونه أُعطي جرعة زائدة، مما تسبب له بضرر في الدماغ، ورغم أن طاقم المستشفى قد تجاوز مستوى الجرعة المحدد بواسطة شركة الأدوية، إلا أن والدي الطفل نجحا في مقاضاة تلك الشركة. في أستراليا، تقتل السيارات، وتصيب حوالي 20,000 حيوان كنغر سنويًا، لذلك تُزوَّد السيارات بصدّام أمامي يحمي حيوانات الكنغر من الضرر الناتج عن الاصطدام، ولكن المشكلة أن هذا الصدّام يشوّش عمل الحساسات في كثير من الأحيان، ويؤدي إلى تفعيل حقائب الهواء دون داعٍ، ولحل هذه المشكلة، تجري شركة جنرال موتورز هولدن، تجارب باستعمال دمية تشبه حيوان كنغر يزن 60 كيلو جرام، وذلك للعثور على أفضل صدّام يحد من إلحاق الضرر بحيوانات الكنغر، وفي الوقت ذاته لا يؤدي إلى تفعيل حقائب الهواء. وفي حين أن أمثلة كالتي سبقت قد تترك آثارًا مدمرة على الشركات، إلا أن قانون مسؤولية المنتج بصورته الحالية المنحازة إلى المستهلك، يحظى بتأييد كبير، ويزعم مؤيدو حماية المستهلك من أمثال رالف نادر، أن قانون مسؤولية المنتج قد كان منحازًا لفترة طويلة لصالح الشركات المصنعة على حساب المستهلكين، وأن شبح الدعاوى القضائية، والتسويات المالية الضخمة، والتعويضات الهائلة، تجبر الشركات على صناعة منتجات آمنة، ورغم أن مناقشة مسؤولية المنتج من جميع نواحيها ليس ممكنًا في هذا الكتاب، لكن من الواضح أن مسؤولية المنتج سوف تظل تحظى بتأثير هائل على الزبائن، والشركات على حد سواء، وليس ذلك فحسب، بل إن تأثيرها سوف يطول -أيضًا- تجار الجملة، والتجزئة، وأصحاب الوكالات، والبائعين، ومقاولي البناء، والمهندسين. رفع القيود الحكومية يُقصد برفع القيود الحكومية؛ تخفيف قيود الحكومة، أو إزالتها بالكلية عن القطاعات التي تمثل "احتكارًا طبيعيًا" مثل قطاع الهاتف، أو الخدمات الأساسية العامة، مثل: الخطوط الجويّة، وشاحنات النقل، وعندما تخضع هذه المجالات للقيود الحكومية، فإنها تكون محميّة من المنافسة، فعلى سبيل المثال: منع مجلس الطيران المدني الأمريكي إنشاء أي خط طيران جديد لأكثر من 40 عامًا، ولم يكن باستطاعة الطائرات إلا أن تحلق وفق المسارات المحددة لها بواسطة المجلس. ولكن، بمرور الوقت، ازدادت الأمور سوءًا، إذ لم يكن لدى الشركات المحميّة من المنافسة أي سبب لتقليص التكاليف، وركّزت بدلًا من ذلك على محاولة التأثير على صنّاع القرار، لاستصدار قرارات في صالحها، كذلك سعت هذه الشركات إلى رفع الأسعار، وتقليل الاستثمارات الجديدة، وهو ما أدى إلى رفع التكاليف، وتراجع جودة الخدمة. لقد خضع العديد من المجالات مثل: الخطوط الجوية، والمصارف، والسكك الحديدية، والاتصالات، وشاحنات النقل، لقيود الحكومة لفترة طويلة للغاية، لذلك أحدث رفع القيود الحكومية صدمة كبيرة في السوق، فقد شهدت جميع هذه المجالات ولادة العديد من المنافسين الجُدد، الذين حاولوا استغلال الفرص التي نتجت عن رفع القيود الحكومية، وفي حين أنه لم تستطع جميع هذه الشركات تحقيق النجاح، لكن المحصلة النهائية كانت اشتداد المنافسة، وانخفاض الأسعار (أحيانًا إلى ما دون قيمة التكلفة)، فيما عانت كثير من الشركات التي كانت مستقرة في يوم من الأيام من خسائر مالية كبيرة. ومع رفع القيود الحكومية، اشتدت وتيرة المنافسة في ظل تخفيف القيود عن الأسعار، أو إزالتها بالكامل، فعلى سبيل المثال: كانت شركة AT&T تتحرك ببطء نحو استخدام الألياف البصرية، فقد كانت الألياف البصريّة تمثل 352,000 كم من شبكة الشركة بحلول عام 1985، ولكن بحلول عام 1994 أصبحت لدى الشركة 3.3 مليون كيلومتر من الألياف البصرية (أي أكثر بقليل من شركتي MCI وSprint)، أما الخطوط الجوية، فقد شهدت بعد تحريرها من قيود مجلس الطيران المدني الأمريكي، ظهور نظام محوري لنقل المسافرين، وبعد أن كان 14% من المسافرين يُضطرون إلى تغيير خطوطهم الجوية، قبل الوصول إلى وجهتهم النهائية في عام 1978، انخفضت هذه النسبة بحلول عام 1995 حتى أصبحت 1% فقط. حماية المستهلك شهدت الولايات المتحدة منذ مطلع القرن العشرين، جهودًا متضافرة لحماية المستهلكين، فعلى سبيل المثال: سعى قانون الغذاء، والدواء، ومواد التجميل الصادر في عام 1938 بشكل رئيس، إلى منع الغش، وإساءة الترويج لهذه الأصناف الثلاث، وتتضمن القوانين الفيدرالية لحماية المستهلك أكثر من 30 تعديلًا، وقانونًا منفصلًا، يتعلق بالغذاء، والدواء، ومواد التجميل، ومن أمثلتها: القانون الأمريكي لحليب الأطفال (1980)، وقانون طباعة المحتوى الغذائي على المغلفات (1990)، ولعل فترة الستينات كانت أكثر فترة شهدت جهودًا لحماية المستهلك، فقد ظهرت في تلك الفترة الثقافة الاستهلاكية، ونشأت حركة شعبية تهدف إلى زيادة قوة المستهلك، وحماية حقوقه، في مواجهة الشركات، وقد تُوجت هذه الجهود بسن قانون أمان المنتجات الاستهلاكية في عام 1972. يشير مصطلح أخلاقيات العمل إلى جملة من المبادئ، والقيم الأخلاقية، التي توجه سلوك الشركات، وتجدر الإشارة إلى أن العديد من قرارات العمل -إن لم يكن معظمها- ينطوي على أبعاد أخلاقية مهمّة، ولنفترض أن شركة تصنع الأحذية الرياضية تدرس إقامة مصنع لها في دولة ذات سمعة سيئة للغاية فيما يتعلق بحقوق الإنسان، صحيح أن هذا المصنع سوف يحسّن من القدرة التنافسية لدى الشركة، ولكن حكومة ذلك البلد سوف تحقق قدرًا كبيرًا من الأرباح، علمًا بأن هذه الأرباح سوف تذهب إلى جيوب النخبة الحاكمة، وليس للشعب الفقير، الذي سوف يعمل في المصنع، نظير أجور زهيدة جدًّا، فهل تمثل زيادة أرباح الشركة مبررًا لدعم حكومة فاسدة؟ تحاول الشركات -دومًا- أن تأخذ القضايا الأخلاقية في الحُسبان، خلال عملية اتخاذ القرار، وذلك على أمل منع السلوك غير الأخلاقي، أو الحد منه على الأقل. إن الفضائح الأخلاقية واردة الحدوث، وهي لا تمر مرور الكرام، حتى مع محاولات الشركات الحثيثة لتلافيها، وهو ما يحتّم على الشركات وضع سياسة أخلاقية تساعدها على التعافي من آثار أي فضيحة أخلاقية محتملة، وتزداد احتمالية ظهور الفضائح الأخلاقية لدى الشركات نتيجة عدد من العوامل، ومنها: مشاعر الكراهية التي يحملها جزء كبير من الجمهور تجاه الشركات اليوم، وميل الصحافة نحو التقارير الاستقصائية، واستعداد العديد من العاملين في الشركات لفضح الانتهاكات، والممارسات غير الأخلاقية. .anntional__paragraph { border: 3px solid #f5f5f5; margin: 20px 0 14px; position: relative; display: block; padding: 25px 30px; } بزنس ويك/هاريس بول أُجريت استطلاعات الرأي على 1035 شخصًا بالغًا بين 25-29 أغسطس 2000، و1009 أشخاصٍ بالغين بين 29 يونيو - 5 يوليو 2000، و1010 أشخاص بالغين بين 9-12 ديسمبر 1999، و1004 أشخاص بالغين بين 23-26 فبراير 1996. تصل دقّة النتائج إلى 3%. النتائج المعروضة جُمعت خلال عام 2000 ما لم يُشر إلى خلاف ذلك. قليل من يشيد بالشركات الأمريكية، والأكثرية يلومونها. يرجع معظم الفضل في الازدهار الذي ساد الولايات المتحدة خلال التسعينات إلى الشركات الأمريكية. أتفق بشدّة أتفق قليلًا أختلف قليلًا أختلف بشدّة لست متأكدًا/لا إجابة 2000 26% 42% 19% 10% 2% 1996 55% * 44%* لقد حصلت الشركات على نفوذ كبير في العديد من نواحي الحياة في الولايات المتحدة. أتفق بشدّة أتفق قليلًا أختلف قليلًا أختلف بشدّة لست متأكدًا/لا إجابة 2000 (أغسطس) 40% 32% 15% 9% 4% 2000 (يونيو) 52% 30% 12% 4% 2% 1996 *71% 28%* بصفة عامّة، ما يصب في صالح الشركات، يصب -أيضًا- في صالح معظم الأمريكيين. أتفق بشدّة أتفق قليلًا أختلف قليلًا أختلف بشدّة لست متأكدًا/لا إجابة 2000 (أغسطس) 14% 33% 27% 22% 4% 2000 (يونيو) 17% 35% 23% 24% 1% 1996 32% 39% 20% 8% 1% ما هو حجم ثقتك بمن يديرون الشركات الكبرى؟ 2000 1999 كبير 19% 15% متوسط 58% 69% قليل 17% 13% لست متأكدًا/لا إجابة 5% 3% زيادة الأرباح أهم بالنسبة إلى الشركات الكبرى من تطوير منتجات آمنة، وموثوقة ذات جودة عالية للمستهلكين. أتفق بشدّة أتفق قليلًا أختلف قليلًا أختلف بشدّة لست متأكدًا/لا إجابة 2000 (أغسطس) 38% 28% 14% 17% 3% * أسئلة مطروحة فقط بصيغة هل تتفق أم لا. انتقادات جور خلال المؤتمر الأخير للحزب الديموقراطي، وجّه نائب الرئيس الأمريكي آل جور انتقادات لعدد من كبرى الشركات، بما في ذلك شركات السجائر، والنفط، والأدوية، والتأمين الصحي، وكذلك الشركات الملوّثة للهواء. هل تتفق أم تختلف مع ما قاله جور؟ أتفق بشدّة أتفق قليلًا أختلف قليلًا أختلف بشدّة لا إجابة 39% 35% 9% 13% 4% تصنيف الشركات بحسب مجال عملها كيف تقيّم المجالات التالية بحسب جودة الخدمات التي تقدّمها للزبائن؟ سيئة فقط سيئة قليلًا جيّدة ممتازة لا أعرف/لا إجابة شركات التأمين الصحي 43% 28% 15% 3% 11% شركات السجائر 43% 30% 14% 5% 8% شركات النفط 39% 35% 16% 3% 7% شركات التأمين 32% 41% 21% 3% 3% شركات الأدوية 27% 37% 26% 5% 5% الخطوط الجوية 22% 41% 25% 3% 9% شركات الهاتف 20% 42% 31% 6% 1% وكالات الأخبار 18% 38% 33% 6% 5% المستشفيات 15% 35% 38% 9% 3% شركات الترفيه 14% 33% 38% 9% 6% شركات السيارات 12% 42% 37% 6% 3% شركات الخدمات المالية 12% 40% 34% 5% 9% شركات الحاسوب 4% 30% 40% 10% 16% منتجات جيّدة وممارسات تجارية سيّئة كيف تقيّم الشركات الأمريكية الكبرى بحسب قدرتها على صناعة منتجات جيّدة، والمنافسة في الاقتصاد العالمي؟ ممتاز جيد جدًا جيد سيء لا أعرف/لا إجابة 2000 18% 50% 26% 5% 1% 1996 14% 44% 33% 9% تنشأ المشاكل الأخلاقية التي تواجه المسوّقين من الخلافات التي قد تطرأ في العلاقة التسويقية، فكل طرف في هذه العلاقة يحمل توقعات معيّنة حول شكل العلاقة، وآلية التعامل، فعلى سبيل المثال: قد ترغب بصفتك مستهلكًا بـ: (1) معاملة جيّدة من مندوب المبيعات. (2) سعر معقول. (3) منتج يتفق مع المواصفات المذكورة في الإعلان. (4) ويعمل بصورة جيّدة. ولكن للأسف، توقعاتك حول هذه العلاقة قد لا تتفق مع توقعات البائع، فقد لا يكون لدى مندوب المبيعات ما يكفي من الوقت للتعامل معك، وقد يكون السعر المعقول بالنسبة إلى البائع أعلى من السعر المعقول بالنسبة إليك، كذلك قد يكون إعلان المنتج مضللًا. ويلخص الجدول رقم 5 القضايا الأخلاقية المتعلقة بالتسويق. القضية النسبة المئوية حسب ردود خبراء التسويق الرشوة هدايا من خارج الشركة، دفع عمولات مشبوهة، "أموال من تحت الطاولة". 15% الإنصاف تقديم مصالح الشركة على الالتزامات العائلية، سرقة جهود الآخرين، حث الزبائن على استعمال خدمات لا يحتاجون إليها، التلاعب بالآخرين. 14% النزاهة الكذب على الزبائن للحصول على الطلبات، الكذب حول خدمات الشركة وقدراتها. 12% السعر التمييز في الأسعار بين الزبائن، طلب أسعار أعلى من الشركات الأخرى التي تقدّم منتجات مشابهة بزعم التفوّق عليها. 12% المنتج أمان المنتج، التعدي على منتجات الآخرين وعلامتهم التجارية، المغالاة في المزاعم حول أداء المنتج، تقديم منتجات غير مفيدة للزبائن. 11% الأفراد طرد الموظفين، توظيف الموظفين الجُدد، تقييم الموظفين الحاليين. 10% السريّة محاولة الحصول على معلومات سريّة أو تنافسيّة، واستغلالها لمصالح شخصيّة. 5% الإعلان الخلط بين المبالغة والتضليل، وتضليل الزبائن. 4% التلاعب بالبيانات تزوير الأرقام، وإساءة استغلال الإحصاءات، أو المعلومات. 4% الشراء التلاعب بعملية اختيار الموردين. 3% في هذا الإستطلاع طُلب من خبراء التسويق وصف أهم القضايا الأخلاقية التي تواجههم في مجال التسويق. رغم أن أخلاقيات العمل ترتبط بشكل أساس بالعلاقة بين المشتري، والبائع، إلا أن أنشطة الشركة قد تؤثر في بعض الأحيان على المجتمع بأكمله، فعلى سبيل المثال: عندما تشتري ثلاجة جديدة، فسوف تكون بحاجة للتخلص من ثلاجتك القديمة، وعندما تلقي الثلاجة القديمة في حاوية القمامة، فهناك احتمالية أن تشكل هذه الثلاجة خطرًا على سلامة الآخرين، أو تلوّث التربة، أو تشوه الصورة الجمالية للمكان، وبالتالي فإن المجتمع بأكمله سوف يتحمل جزءًا من تكلفة شرائك للثلاجة الجديدة. يسلط المثال السابق الضوء على مفهوم المسؤولية الاجتماعية، ونعني بذلك أن الشركات هي في الحقيقة جزء من المجتمع، وهي تتحمل مسؤولية أفعالها تجاهه، ويروّج بعض المسوّقين لمفهوم التسويق الاجتماعي، والذي يدعو الشركات إلى البحث عن حاجات زبائنها، وإشباعها على نحو يحقق رفاهية المجتمع ككل، ويعرّف ألان أندرسون التسويق الاجتماعي بأنه: تكييف أساليب التسويق التجاري المعتادة، للتأثير على سلوك الشرائح المستهدفة، على نحو يحقق رفاهية الأفراد، والمجتمع ككل. خلاصة القول: لا شكّ أن التسويق الاجتماعي أصبح يكتسب أهمية متزايدة، وأنه بات يساعد كثيرًا من المسوّقين على تحقيق الميزة التنافسية. العوامل السياسية والاقتصادية المؤثرة على التسويق تؤثر العديد من القوى الاقتصاديّة على قدرة الشركة على المنافسة، وكذلك على استعداد الزبون، وقدرته على شراء المنتجات، والخدمات. إن الاقتصاد في حالة تغيّر مستمر، ومعدلات الفائدة تصعد، وتهبط، والتضخم يزداد، ويتراجع، كل ذلك يؤثر على قدرة الزبون، واستعداده للشراء، ولكن أهم العوامل الاقتصادية المؤثرة هي: القوة الشرائية لدى الزبون، والدورة الاقتصادية، أو دورة الأعمال. القوة الشرائية لدى المستهلك يؤثر الاقتصاد على القوّة الشرائيّة، والتي تمثل قدرة المستهلك على الشراء، فعلى سبيل المثال: تزداد القوة الشرائية لدى المستهلك عندما تتراجع الأسعار، أو تزداد قيمة الدولار بالنسبة إلى العملات الأجنبية، في المقابل، تنخفض القوة الشرائية في حالة التضخم مثلًا. وفيما يأتي قائمة بعدد من الجوانب المتعلقة بالقوّة الشرائيّة لدى المستهلك. القوة الشرائية: قدرة المستهلك على الشراء. الدخل: الأموال التي يحصل عليها الفرد على صورة أجور، أو استثمارات، أو معاشات، أو حتى دعم حكومي. الدخل القابل للتصرف: الدخل المتبقي للإنفاق بعد دفع الضرائب. الدخل التقديري: هو الدخل القابل للتصرف بعد اقتطاع المصروفات الأساسية (مثل الطعام، والسكن، والملابس). الائتمان: قدرة الفرد على شراء المنتج في الوقت الحالي، والدفع في وقت لاحق. الثروة: تراكم الدخول السابقة، والموارد الأخرى، مثل: حسابات التوفير، والمجوهرات، والعقارات، ونحوها. الاستعداد للإنفاق: حجم ما يرغب الفرد إنفاقه من الدخل القابل للتصرف، ووجوه إنفاقه. أنماط الإنفاق الاستهلاكي: كمية الأموال التي ينفقها المستهلك، على أنواع معيّنة من المنتجات، والخدمات في كل عام. أنماط الإنفاق الشاملة: كمية الدخل التي ينفقها مجموع الأفراد، على أصناف من المنتجات، والخدمات. أنماط الإنفاق على المنتجات: كمية الدخل المنفقة على منتجات محددة، ضمن صنف معيّن من المنتجات. تتناول اللمحة الآتية بعض المفاهيم الموضحة أعلاه. لمحة: أنماط الإنفاق يشهد اقتصاد الولايات المتحدة نموًا بمعدلات لم يشهدها منذ الستينات، فالبطالة، والتضخم في أدنى مستوياتها منذ عقود، وسوق الأوراق المالية يسجل قدرًا كبيرًا من الانتظام، ويفسر بعض الاقتصاديين ذلك بأن نظامًا اقتصاديًا جديدًا على وشك الظهور بفضل تقليص العجز، وانخفاض معدلات الفائدة، والتقدّم التقني، بينما يرجع آخرون ذلك إلى الأزمة المالية الآسيوية، ومع ذلك، يتساءل الجميع: إلى متى سوف يستمر هذا الاتجاه في السوق؟ (في الحقيقة، انتهى هذا الاتجاه في عام 2000). ورغم حالة التوتر في السوق، نستطيع الاطمئنان على الأقل بأن الاقتصاد بات أكثر استقرارًا مما يعتقد الكثيرون، وذلك بسبب استقرار الإنفاق الاستهلاكي -الذي يمثل ثلثي الناتج الاقتصادي في البلاد- بدرجة كبيرة. لكن كيف يكون ذلك ممكنًا؟ ألم تكن جميع وسائل الإعلام تحتفي بازدياد الإنفاق في المجتمع الأمريكي؟ لقد شهد الإنفاق الاستهلاكي نموًا بالفعل، ولكن على المستوى الكلي الإجمالي، فالبلاد تشهد نموًا سكانيًا، وازديادًا في عدد الأسر، في حين أن جيل طفرة المواليد قد وصل إلى ذروة سنوات الإنفاق، فأصغرهم الآن في سن الخامسة والثلاثين، ولكن توجهات الإنفاق لدى الأسر تروي قصة مختلفة تمامًا، فعلى الرغم من انخفاض معدلات البطالة، وارتفاع الأجور؛ إلا أن الإنفاق لدى الأسر الأمريكية خلال العقد الماضي بات يتسم بالحذر الشديد؛ فقد تراجع متوسط إنفاق الأسرة بين عامي 1987 و1997 على الطعام بمعدل 13%، وعلى الأجهزة الأساسية بمعدل 25%، وعلى المشروبات الكحولية بمعدل 24%، وعلى الجرائد، والكتب، والمجلات، بمعدل 18%، وعلى الملابس بمعدل 15%. تُعد دورة حياة الأسرة، أهم مؤشرات الإنفاق، فالأسر التي تتكون من والدين في العشرينات من عمرهما، تنفق بمعدل أقل على معظم المنتجات، والخدمات، وذلك بسبب صغر حجم الأسرة، وانخفاض الدخل، ويصل الإنفاق إلى أعلى مستوى له في منتصف العمر، وذلك بسبب ازدياد حجم الأسرة، ووصول الدخل إلى ذروته، ثم يتراجع مجددًا مع التقدّم في السن، وذلك بسبب تراجع حجم الأسرة وانخفاض الدخل. جميع هذه العوامل، بالإضافة إلى تذبذب معدل المواليد خلال العقود الماضية، يجعل التسويق الاستهلاكي عملية صعبة، ومعقّدة، وقد بات المسوقون اليوم يدركون أن ممارسة الأعمال التجارية أشبه ببناء منزل على فوهة بركان. دورة الاقتصاد تؤثر التقلبات الاقتصادية على العرض، والطلب، والقوة الشرائية، والاستعداد للإنفاق، وشدة المنافسة، ولكن هذه التقلبات تسير وفق نمط عام يُعرف باسم: دورة الاقتصاد، والتي تتكون من أربع مراحل، وهي: الازدهار، والركود، والكساد، والانتعاش. الازدهار يعدُّ الازدهار عن فترة ينمو فيها الاقتصاد، وتنخفض فيها البطالة، وتزداد القوة الشرائية لدى المستهلكين، ويشتد الطلب على المنتجات. وخلال الازدهار يكون الدخل القابل للتصرف لدى المستهلكين كبيرًا، وهو ما يدفعهم إلى تحسين جودة الحياة من خلال شراء منتجات، وخدمات عالية الجودة، ذات سعر مرتفع، وقد شهد الاقتصاد الأمريكي فترة ازدهار بين عامي 1991 و2000. وبالنسبة إلى المسوّقين، تكثر الفرص خلال فترة الازدهار، الأمر الذي يدفعهم إلى توسيع خطوط الإنتاج لاستغلال الاستعداد الكبير لدى المستهلكين للشراء. الركود تتسم فترة الركود بتراجع معدلات النمو الاقتصادي، والقوة الشرائية لدى المستهلكين، في مقابل ارتفاع معدلات البطالة، ويحصل الركود -عادةً- بعد فترات الازدهار والتخضم، وخلال الركود، تنخفض القوة الشرائية لدى المستهلكين، الذين ينشغلون بتسديد الدّيون التي تراكمت عليهم نتيجة الشراء عن طريق الائتمان في فترة الازدهار، في هذه الفترة تقل الفرص التسويقية، وذلك بسبب انخفاض القوة الشرائية، وتركيز المستهلكين على المنتجات الأساسية فقط. الكساد تمثل مرحلة الكساد أخطر مراحل الاقتصاد، فهي تتسم بازدياد البطالة، وانخفاض القوة الشرائية بدرجة كبيرة، ويمكن القول: إن جميع المؤشرات الاقتصادية تتراجع في هذه الفترة، أما الزبائن، فيصبحون عاجزين عن شراء المنتجات، وخصوصًا المنتجات باهظة الثمن. وفي حين أن العديد من المسوّقين يفشلون في التعامل مع هذه الفترة، إلا أن المسوّقين البارعين يستطيعون الحصول على حصتهم من السوق رغم كل الظروف. الانتعاش يُعد الانتعاش مرحلة اقتصادية معقّدة، ففيها ترتفع بعض المؤشرات الاقتصادية، فيما تظل مؤشرات اقتصادية أخرى كما هي، أو حتى تتراجع، وترجع مرحلة الانتعاش في معظمها إلى أمور غير محسوسة، مثل ازدياد الثقة لدى المستهلكين، أو اعتقاد الشركات بأن الأمور سوف تتحسن في المستقبل. أما بالنسبة إلى المسوّقين، فمن الضروري تحديد سرعة عودة الاقتصاد إلى وضع الانتعاش، وقد تقود التوقعات غير الصحيحة بعض الشركات إلى أن ترهق نفسها دون داعٍ، وخصوصًا أن تغيير عادات الشراء التي اعتاد عليها المستهلكون في الأوقات الاقتصادية الصعبة، قد يستغرق بعض الوقت. إن الاقتصاد دائري بطبيعته، أي أن هذه الدائرة سوف تحدث بالتأكيد، ولكننا لا نستطيع توقّع موعد وقوع كل مرحلة منها، وكذلك مدى حدتها، وهو ما يوجب على الشركات إعداد تقديرات تتعلق بالسيولة المالية، والأفراد، والموارد، فعلى سبيل المثال: قد تصبح شركة أقل جرأة في قراراتها، عندما يتكون لديها اعتقاد بأن الاقتصاد ليس متجهًا نحو النمو، وإذا كانت هذه الشركة محقّة في افتراضها، فسوف تبلي بلاء حسنًا في الأوقات المالية الصعبة، أما إن كانت مخطئة، فسوف تتفوق الشركات الأخرى ذات القرارات الجرئية عليها، كذلك يجب على الشركات التنبؤ بالعوامل الاقتصادية المختلفة، مثل: معدلات الفائدة، والتضخم، وحجم القوى العاملة، وطبيعتها، وتوفر الموارد المختلفة من مصادر الطاقة، والمواد الخام. تأثير التقنية على التسويق هل السيارة الكهربائية هي سيارة المستقبل؟ يزعم أنصار السيارات الكهربائية أنها لا تصدر أي عوادم ملوّثة للبيئة، ولكن حسب الوكالة الأمريكية لحماية البيئة فالتقنية المستخدمة في شحن بطاريات هذه السيارات تتولى هذا الدور، في المقابل، يزعم معارضو السيارات الكهربائية أنه كلما ازداد عددها، ازداد التلوث المنبعث من محطات توليد الطاقة الكهربائية، ربما تكون مطلعًا على الجدل الدائر حول السيارات التقليدية، وتأثيرها على البيئة، ولكن إذا لم تكن السيارات الكهربائية مختلفة عن السيارات التقليدية، فسوف نشهد بعض الجدالات المثيرة للاهتمام، بين أنصار هذه السيارات، ومعارضيها، في الحقيقة، يعتقد بعض المديرين التنفيذيين في شركات السيارات التقليدية، أن تقرير الوكالة الأمريكية لحماية البيئة لم يكن صريحًا بما فيه الكفاية في انتقاده لهذه السيارات. تؤثر التقنية على المسوّقين من نواحٍ متعددة. أولًا- تتيح التقنيات المتقدّمة إنتاج منتجات جديدة بوتيرة سريعة للغاية، وهو ما يمثل تهديدًا لجميع المنتجات الموجودة في السوق في الوقت الحالي. ثانيًا- تزيد التقنية من حدّة المنافسة بين الشركات، وتساعد على طرح منتجات بديلة عن المنتجات الموجودة في السوق. ثالثًا- تُعد المنتجات التقنية المبتكرة، التي تحسن الأداء، وتقلل التكاليف، أفضلَ وسيلة لحماية الحصة السوقية، أو زيادتها، دون التخلي عن هامش الأرباح، فجميع هذه العوامل تنطبق بصورة خاصة على السوق اليوم، إذ باتت العديد من الأسواق تشهد نموًا ثابتًا، أو بطيئًا، مع وجود فائض في القدرة. إن التاريخ حافل بالعديد من الأمثلة لشركات خسرت ميزتها التنافسية -وربما عملها بالكامل- لأن منافسًا دخل إلى السوق بمنتج أفضل، من ناحية الأداء، والتكلفة الاقتصادية، وذلك ليس مقتصرًا على شركات صغيرة، أو ضعيفة، بل حصل ذلك -أيضًا- مع شركات عملاقة مثل آي بي إم (IBM)، وجنرال إلكتريك (General Electric)، ,إيه تي آند تي (AT&T). فرغم سيطرة شركة آي بي إم على سوق الحاسوب، إلا أنها خسرت موقعها خلال السبعينات، لصالح عدد من الشركات الأصغر حجمًا، التي نجحت في تطوير حواسيب قوّية وصغيرة، حلت محل الحواسيب العملاقة الخاصة بشركة آي بي إم. لذا يجب على جميع الشركات أن تضع تقديرات حول مستقبل التقنية، وآثارها المحتملة على الأنشطة التسويقية، فتأثيرها لا يمكن تجاهله، أو التغاضي عنه، فعلى سبيل المثال: كان اليابانيون يستعملون الدوائر الإلكترونية في مفاتيح التحكّم، في المقابل لم تستجب الشركات الأمريكية للتغير بسرعة، وظلت تستخدم في منتجاتها مفاتيح التحكّم الإلكتروميكانيكية. إن الجميع يستمتع بالتفكير حول المستقبل، وتخيّل ما قد تصل إليه التقنيات الحديثة، لذلك دعونا نسافر في الزمن بضع سنوات إلى الأمام، لنتعرف على الفرص التي قد تتيحها تلك التقنية للمسوّقين: ما رأيك بإعلانات لا تستهدف فئات بشرية، أو نفسيّة معينة، وإنما تستهدف كل زبون بعينه، أي إعلانات تعرف بالضبّط ماذا يريد الزبون وماذا يحتاج؟ ما رأيك ببيت مليء بالأجهزة الكهربائية الذكيّة المرتبطة بالإنترنت، على سبيل المثال: ثلاجة تخبرك عندما ينفد الحليب منك، أو غسالة تتصل بفني الصيانة عندما تتعطل؟ ما رأيك بهاتف يعرف موقعك، ويستطيع توجيهك إلى أي مطعم تريده، أو جهاز فيديو بحجم كف اليد؟ ما رأيك بتلفاز يبث دعاية للبيتزا، ولكن هذه الدعاية تتيح لك طلب البيتزا بضغطة زر من خلال التكامل بين التلفاز، والإنترنت؟ ما رأيك بملابس داخلية تقيس مستوى السكر في الدم، وتحقنك بالأنسولين تلقائيًا عندما يرتفع، أو ملابس تستشعر أن هناك سكتة قلبية قادمة فتنبهك إلى ضرورة تناول الدواء؟ لقد باتت جميع هذه المعجزات ممكنة في عالم اليوم المدهش، وهي ليست مجرد تقنيات في المختبر، وإنما نماذج أولية على وشك الدخول إلى السوق. تفتح التقنيات آفاقًا كبيرة أمام المسوّقين، ولكنها -أيضًا- تنطوي على العديد من العقبات، فقد لا تكون شركات الهاتف، والإنترنت والأجهزة الكهربائية، وحتى الزبائن، متحمسين لاستعمال الحاسوب. التسويق المتكامل ليس الجميع محبًا للإنترنت ما زال هناك العديد من الأشخاص بعيدين تمامًا عن الإنترنت، وهم عازمون على البقاء كذلك، وهذا ينطبق على العديد من الأغنياء والمشاهير، ومن هؤلاء مارك مكورماك، وكيل لاعب الجولف الشهير تايجر ودس، ولاعبتا التنس الشهيرتان: فينوس، وسيرينا ويليامز، إذ يحيط مارك نفسه بالعديد من الخبراء التقنيين، ولكنه لم يستخدم حاسوبًا في حياته، أما الممثلة داريل هانا فهي تمتلك حاسوبًا، ولكنها لم تشغله منذ ما يزيد عن ثلاث سنوات، وأما المؤلف هارلان إليسون، فهو يكتب قصصه القصيرة باستخدام آلة طباعة يدوية قديمة، ويعتقد أن الإنترنت "مضيعة كبيرة للوقت". ترجمة -وبتصرف- للفصل (External considerations in marketing) من كتاب Core Concepts of Marketing اقرأ أيضًا المقال التالي: أهم الاتجاهات الاجتماعية المؤثرة على عملية التسويق المقال السابق: سلوك الشراء لدى الشركات النسخة العربية الكاملة لكتاب مدخل إلى التسويق1 نقطة