Blog

Guide to 0.96″ OLED Display Module (128×64) for Arduino

in RTL direction * (or the app container overrides direction itself - it does). */ if ( ! defined( 'ABSPATH' ) ) { exit; // Prevent direct file access } /* ───────────────────────────────────────────── 1. REGISTER SHORTCODE ──────────────────────────────────────────────── */ add_shortcode( 'ekp_orders_manager', 'ekp_orders_manager_render' ); function ekp_orders_manager_render( $atts ) { // Enqueue assets only when the shortcode is actually used ekp_orders_manager_enqueue_assets(); // The React app mounts into this div return '
'; } /* ───────────────────────────────────────────── 2. ENQUEUE ASSETS (CDN – no build step needed) ──────────────────────────────────────────────── */ function ekp_orders_manager_enqueue_assets() { // Google Fonts: Cairo (Arabic) + JetBrains Mono wp_enqueue_style( 'ekp-google-fonts', 'https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap', [], null ); // Tailwind CSS (CDN – suitable for non-production or private admin pages) wp_enqueue_script( 'ekp-tailwind', 'https://cdn.tailwindcss.com', [], null, false ); // React 18 + ReactDOM (production UMD) wp_enqueue_script( 'ekp-react', 'https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js', [], '18.2.0', true ); wp_enqueue_script( 'ekp-react-dom', 'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js', [ 'ekp-react' ], '18.2.0', true ); // Lucide React Icons (UMD build) wp_enqueue_script( 'ekp-lucide', 'https://unpkg.com/lucide-react@0.383.0/dist/umd/lucide-react.js', [ 'ekp-react' ], '0.383.0', true ); // Framer Motion / Motion for React (UMD) wp_enqueue_script( 'ekp-motion', 'https://cdn.jsdelivr.net/npm/motion@latest/dist/motion.js', [ 'ekp-react' ], null, true ); // The inline app styles wp_add_inline_style( 'ekp-google-fonts', ekp_orders_manager_inline_css() ); // The compiled app script (inline – avoids a separate file upload) wp_add_inline_script( 'ekp-react-dom', ekp_orders_manager_app_js(), 'after' ); } /* ───────────────────────────────────────────── 3. INLINE CSS ──────────────────────────────────────────────── */ function ekp_orders_manager_inline_css() { return <<<'CSS' #ekp-orders-root { font-family: "Cairo", "Segoe UI", ui-sans-serif, system-ui, sans-serif; background-color: #f1f5f9; direction: rtl; text-align: right; } #ekp-orders-root .geometric-grid { background-image: radial-gradient(#cbd5e1 1px, transparent 1px); background-size: 24px 24px; } #ekp-orders-root .copy-pulse:active { transform: scale(0.95); background-color: #e2e8f0; transition: all 0.1s; } #ekp-orders-root .live-indicator { width: 8px; height: 8px; background: #22c55e; border-radius: 50%; box-shadow: 0 0 0 4px rgba(34,197,94,0.2); display: inline-block; } #ekp-orders-root .live-indicator.saving { background: #f59e0b; box-shadow: 0 0 0 4px rgba(245,158,11,0.2); } #ekp-orders-root ::-webkit-scrollbar { width: 8px; height: 8px; } #ekp-orders-root ::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 999px; } #ekp-orders-root ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 999px; } #ekp-orders-root ::-webkit-scrollbar-thumb:hover { background: #94a3b8; } @keyframes ekpSlideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } #ekp-orders-root .slide-up { animation: ekpSlideUp 0.3s cubic-bezier(0.16,1,0.3,1) forwards; } CSS; } /* ───────────────────────────────────────────── 4. INLINE JAVASCRIPT (the full React app) The app is compiled from the uploaded source code into a single self-contained IIFE that uses global React, ReactDOM, LucideReact, and Motion objects loaded by the CDN scripts above. ──────────────────────────────────────────────── */ function ekp_orders_manager_app_js() { // This long string is the transpiled version of App.tsx + types + mockData. // It uses React.createElement (no JSX) so it runs in any browser without // a build step, and references the globals exposed by the CDN UMD bundles. return <<<'JS' (function () { "use strict"; // ── Polyfill: wait until all CDN scripts have loaded ───────────────────────── function waitForDeps(callback) { if (window.React && window.ReactDOM && window.LucideReact && window.Motion) { callback(); } else { setTimeout(function () { waitForDeps(callback); }, 50); } } waitForDeps(function () { const React = window.React; const ReactDOM = window.ReactDOM; const { useState, useEffect, useMemo, useRef } = React; const { Copy, Check, Trash2, Plus, Search, Share2, Settings, Filter, TrendingUp, Package, Clock, Truck, FileSpreadsheet, User, MapPin, Phone, Mail, FileText, X, ChevronDown, ChevronUp, RefreshCw, AlertCircle, SlidersHorizontal, Info, CheckCircle2, Trash, ShoppingBag, ExternalLink, Undo } = window.LucideReact; const { motion, AnimatePresence } = window.Motion; const h = React.createElement; /* ─── DATA ───────────────────────────────────────────────────────────────── */ const EGYPTIAN_GOVERNORATES = { 'القاهرة':'القاهرة','الجيزة':'الجيزة','الإسكندرية':'الإسكندرية', 'البحيرة':'البحيرة','المنوفية':'المنوفية','الغربية':'الغربية', 'كفر الشيخ':'كفر الشيخ','الدقهلية':'الدقهلية','دمياط':'دمياط', 'بورسعيد':'بورسعيد','الإسماعيلية':'الإسماعيلية','السويس':'السويس', 'شمال سيناء':'شمال سيناء','جنوب سيناء':'جنوب سيناء', 'الشرقية':'الشرقية','القليوبية':'القليوبية','الفيوم':'الفيوم', 'بني سويف':'بني سويف','المنيا':'المنيا','أسيوط':'أسيوط', 'سوهاج':'سوهاج','قنا':'قنا','الأقصر':'الأقصر','أسوان':'أسوان', 'البحر الأحمر':'البحر الأحمر','الوادي الجديد':'الوادي الجديد','مطروح':'مطروح', }; const MOCK_PRODUCTS = [ { id:101, name:'هودي شتوي أسود ميلتون مبطن', sku:'HD-BLK-MLT', price:450, stock:24 }, { id:102, name:'ساعة يد كلاسيك جلد بني مقاومة للماء', sku:'WT-BRW-C', price:850, stock:15 }, { id:103, name:'حذاء رياضي مريح أبيض نعل طبي', sku:'SN-WHT-MD', price:620, stock:30 }, { id:104, name:'شاحن لاسلكي مغناطيسي سريع 15 واط', sku:'CH-MAG-15W', price:350, stock:45 }, { id:105, name:'سماعة بلوتوث رياضية عازلة للضوضاء', sku:'ST-ANC-SP', price:490, stock:12 }, { id:106, name:'نظارة شمسية عصرية عدسات مستقطبة', sku:'SG-MOD-PL', price:280, stock:8 }, { id:107, name:'محفظة جلد رجالي طبيعي بني داكن', sku:'WL-LTR-N', price:190, stock:50 }, { id:108, name:'باور بانك 20,000 مللي أمبير شحن سريع', sku:'PB-FAST-20K', price:750, stock:18 }, { id:109, name:'جاكيت جينز كلاسيك أزرق', sku:'JK-DNM-BLU', price:580, stock:22 }, { id:110, name:'كاب قطن أسود سادة قابل للتعديل', sku:'CP-BLK-CTN', price:95, stock:100 }, ]; const INITIAL_ORDERS = [ { id:9401, customerName:'محمود', customerLastName:'أحمد فودة', email:'mahmoud.fouda@gmail.com', phone:'01012345678', phone2:'01555621487', address1:'14 شارع جامعة الدول العربية - المهندسين', address2:'الدور الرابع، شقة 12', city:'الجيزة', date:'2026-06-11 08:34', status:'processing', paymentMethod:'الدفع عند الاستلام (COD)', shippingTotal:50, discountTotal:25, trackingNumber:'EG49823429183P', customerNote:'يرجى الاتصال بي قبل التوصيل بساعة للتأكيد.', items:[ { id:1, productId:101, name:'هودي شتوي أسود ميلتون مبطن', price:450, qty:2, isPrepared:true, discountValue:0, discountType:'fixed' }, { id:2, productId:104, name:'شاحن لاسلكي مغناطيسي سريع 15 واط', price:350, qty:1, isPrepared:false, discountValue:10, discountType:'percent' } ] }, { id:9402, customerName:'سارة', customerLastName:'عادل شاهين', email:'sara.shaheen@outlook.com', phone:'01123456789', phone2:'', address1:'بلازا 34، متفرع من شارع التسعين الشمالي', address2:'مكتب رقم 5ب', city:'القاهرة', date:'2026-06-11 07:12', status:'pending', paymentMethod:'بطاقة ائتمانية (فوري)', shippingTotal:40, discountTotal:0, trackingNumber:'', customerNote:'', items:[ { id:3, productId:105, name:'سماعة بلوتوث رياضية عازلة للضوضاء', price:490, qty:1, isPrepared:false, discountValue:0, discountType:'fixed' }, { id:4, productId:106, name:'نظارة شمسية عصرية عدسات مستقطبة', price:280, qty:2, isPrepared:false, discountValue:15, discountType:'percent' } ] }, { id:9403, customerName:'خالد', customerLastName:'إبراهيم النجار', email:'khalid.n@gmail.com', phone:'01234567890', phone2:'', address1:'12 شارع النيل، منزل مستقل', address2:'', city:'أسيوط', date:'2026-06-10 21:05', status:'completed', paymentMethod:'فودافون كاش', shippingTotal:70, discountTotal:50, trackingNumber:'EG77652914501P', customerNote:'', items:[ { id:5, productId:102, name:'ساعة يد كلاسيك جلد بني مقاومة للماء', price:850, qty:1, isPrepared:true, discountValue:0, discountType:'fixed' }, { id:6, productId:107, name:'محفظة جلد رجالي طبيعي بني داكن', price:190, qty:1, isPrepared:true, discountValue:0, discountType:'fixed' } ] }, ]; const statusTranslations = { pending:'⏳ انتظار', processing:'🔄 معالجة', completed:'✅ مكتمل', 'on-hold':'⏸ معلق', cancelled:'❌ ملغي', refunded:'↩️ مسترجع', }; const statusColorClasses = { pending: 'bg-amber-50 text-amber-700 border-amber-100', processing: 'bg-blue-50 text-blue-700 border-blue-100', completed: 'bg-emerald-50 text-emerald-700 border-emerald-100', 'on-hold': 'bg-rose-50 text-rose-700 border-rose-100', cancelled: 'bg-slate-100 text-slate-600 border-slate-200', refunded: 'bg-indigo-50 text-indigo-700 border-indigo-100', }; /* ─── HELPERS ────────────────────────────────────────────────────────────── */ function CalendarIcon({ className }) { return h('svg', { xmlns:'http://www.w3.org/2000/svg', width:24, height:24, viewBox:'0 0 24 24', fill:'none', stroke:'currentColor', strokeWidth:2.5, strokeLinecap:'round', strokeLinejoin:'round', className }, h('path',{d:'M8 2v4'}), h('path',{d:'M16 2v4'}), h('rect',{width:18,height:18,x:3,y:4,rx:2}), h('path',{d:'M3 10h18'}) ); } /* ─── MAIN APP COMPONENT ─────────────────────────────────────────────────── */ function App() { const [orders, setOrders] = useState([]); const [products, setProducts] = useState(MOCK_PRODUCTS); const [selectedOrderIds, setSelectedOrderIds] = useState([]); const [expandedOrderId, setExpandedOrderId] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [governorateFilter, setGovernorateFilter] = useState('all'); const [selectedItemIdsInOrder, setSelectedItemIdsInOrder] = useState({}); const [productPickerOrderId, setProductPickerOrderId] = useState(null); const [pickerSearchQuery, setPickerSearchQuery] = useState(''); const [pickerSelections, setPickerSelections] = useState({}); const [bulkStatusToApply, setBulkStatusToApply] = useState(''); const [toast, setToast] = useState(null); const [historyStack, setHistoryStack] = useState([]); const [isSavingLive, setIsSavingLive] = useState(false); const [lastSavedTime, setLastSavedTime] = useState(''); const [copiedTracking, setCopiedTracking] = useState(null); const [waModalOrder, setWaModalOrder] = useState(null); const [waCustomPhone, setWaCustomPhone] = useState(''); const [showOrderCreator, setShowOrderCreator] = useState(false); const [newOrderForm, setNewOrderForm] = useState({ customerName:'', customerLastName:'', email:'', phone:'', phone2:'', address1:'', address2:'', city:'القاهرة', paymentMethod:'الدفع عند الاستلام (COD)', shippingTotal:50, customerNote:'', }); /* INITIAL LOAD */ useEffect(() => { const saved = localStorage.getItem('ekp_orders'); try { setOrders(saved ? JSON.parse(saved) : INITIAL_ORDERS); } catch(e) { setOrders(INITIAL_ORDERS); } if (!saved) localStorage.setItem('ekp_orders', JSON.stringify(INITIAL_ORDERS)); const savedP = localStorage.getItem('ekp_products'); try { setProducts(savedP ? JSON.parse(savedP) : MOCK_PRODUCTS); } catch(e) { setProducts(MOCK_PRODUCTS); } if (!savedP) localStorage.setItem('ekp_products', JSON.stringify(MOCK_PRODUCTS)); setLastSavedTime(new Date().toLocaleTimeString('ar-EG',{hour:'2-digit',minute:'2-digit'})); }, []); /* SAVE WRAPPER */ function saveStateToStorage(updated) { setIsSavingLive(true); setOrders(updated); localStorage.setItem('ekp_orders', JSON.stringify(updated)); setTimeout(() => { setIsSavingLive(false); setLastSavedTime(new Date().toLocaleTimeString('ar-EG',{hour:'2-digit',minute:'2-digit'})); }, 450); } function pushToHistory(cur) { setHistoryStack(prev => [...prev.slice(-10), JSON.parse(JSON.stringify(cur))]); } /* TOAST */ function showToast(message, type='success') { const id = Date.now(); setToast({ message, type, id }); setTimeout(() => setToast(prev => prev?.id===id ? null : prev), 4000); } /* UNDO */ function handleUndo() { if (!historyStack.length) return; const prev = historyStack[historyStack.length-1]; setHistoryStack(h => h.slice(0,-1)); saveStateToStorage(prev); showToast('↩️ تم التراجع عن الخطوة السابقة!', 'info'); } /* COPY TRACKING */ function handleCopyTracking(tracking, e) { e.stopPropagation(); if (!tracking) return; navigator.clipboard.writeText(tracking).then(() => { setCopiedTracking(tracking); showToast('📋 تم نسخ رقم التتبع بنجاح: ' + tracking, 'success'); setTimeout(() => setCopiedTracking(null), 2000); }); } /* STATS */ const stats = useMemo(() => { const totalSales = orders.filter(o=>o.status==='completed').reduce((sum,o)=>{ const it = o.items.reduce((is,item)=>{ const d = item.discountType==='percent' ? item.price*item.qty*(item.discountValue/100) : item.discountValue*item.qty; return is + item.price*item.qty - d; },0); return sum + it + o.shippingTotal - o.discountTotal; },0); return { totalSales, totalOrders: orders.length, pendingOrders: orders.filter(o=>o.status==='pending'||o.status==='processing').length, completedOrders:orders.filter(o=>o.status==='completed').length, holdOrders: orders.filter(o=>o.status==='on-hold').length, }; }, [orders]); /* FILTERED ORDERS */ const filteredOrders = useMemo(() => { return orders.filter(o => { const q = searchQuery.toLowerCase(); const matchQ = o.id.toString().includes(searchQuery) || (o.customerName && o.customerName.toLowerCase().includes(q)) || (o.customerLastName && o.customerLastName.toLowerCase().includes(q)) || (o.phone && o.phone.includes(searchQuery)) || (o.email && o.email.toLowerCase().includes(q)) || (o.trackingNumber && o.trackingNumber.toLowerCase().includes(q)); const matchS = statusFilter==='all' || o.status===statusFilter; const matchG = governorateFilter==='all' || o.city===governorateFilter; return matchQ && matchS && matchG; }); }, [orders, searchQuery, statusFilter, governorateFilter]); /* BULK STATUS */ function handleBulkStatusChange(newStatus) { if (!selectedOrderIds.length) { showToast('⚠️ يرجى تحديد أوردر واحد على الأكثر أولاً!','error'); return; } pushToHistory(orders); const updated = orders.map(o => selectedOrderIds.includes(o.id) ? {...o, status:newStatus} : o); saveStateToStorage(updated); setSelectedOrderIds([]); showToast('✅ تم تحديث حالة ' + selectedOrderIds.length + ' أوردرات إلى [' + statusTranslations[newStatus] + '] بنجاح!', 'success'); } /* FIELD UPDATE */ function handleOrderFieldChange(orderId, field, value) { pushToHistory(orders); saveStateToStorage(orders.map(o => o.id===orderId ? {...o, [field]:value} : o)); } /* ITEM QTY/PRICE */ function handleItemQtyPriceChange(orderId, itemId, change) { pushToHistory(orders); saveStateToStorage(orders.map(o => { if (o.id!==orderId) return o; return {...o, items: o.items.map(item => item.id!==itemId ? item : { ...item, qty: change.qty !== undefined ? Math.max(1, change.qty) : item.qty, price: change.price !== undefined ? Math.max(0, change.price) : item.price, })}; })); } /* ITEM DISCOUNT */ function handleItemDiscountChange(orderId, itemId, value, type) { pushToHistory(orders); saveStateToStorage(orders.map(o => { if (o.id!==orderId) return o; return {...o, items: o.items.map(item => item.id!==itemId ? item : { ...item, discountValue: isNaN(value) ? 0 : Math.max(0,value), discountType: type })}; })); } /* ITEM PREPARED */ function handleToggleItemPrepared(orderId, itemId, isPrepared) { pushToHistory(orders); saveStateToStorage(orders.map(o => o.id!==orderId ? o : { ...o, items: o.items.map(it => it.id!==itemId ? it : {...it, isPrepared}) })); showToast(isPrepared ? '🟢 تم تعليم الصنف كـ جاهز' : '🟡 تم إلغاء تجهيز الصنف', 'info'); } /* ITEM SELECTION */ function handleToggleItemSelect(orderId, itemId) { setSelectedItemIdsInOrder(prev => { const cur = prev[orderId] || []; return {...prev, [orderId]: cur.includes(itemId) ? cur.filter(id=>id!==itemId) : [...cur, itemId]}; }); } function handleSelectAllItems(orderId, items, select) { setSelectedItemIdsInOrder(prev => ({...prev, [orderId]: select ? items.map(i=>i.id) : []})); } /* DELETE ITEMS */ function handleDeleteSelectedItems(orderId) { const ids = selectedItemIdsInOrder[orderId] || []; if (!ids.length) { showToast('⚠️ يرجى اختيار صنف أو أكثر أولاً!','error'); return; } if (!confirm('هل أنت متأكد من حذف ' + ids.length + ' صنف/أصناف؟')) return; pushToHistory(orders); saveStateToStorage(orders.map(o => o.id!==orderId ? o : {...o, items: o.items.filter(item=>!ids.includes(item.id))})); setSelectedItemIdsInOrder(prev => ({...prev, [orderId]: []})); showToast('🗑️ تم حذف الأصناف المحددة من الطلب #' + orderId + ' بنجاح!', 'success'); } function handleUnsolicitedDeleteItem(orderId, itemId) { pushToHistory(orders); saveStateToStorage(orders.map(o => o.id!==orderId ? o : {...o, items: o.items.filter(item=>item.id!==itemId)})); showToast('🗑️ تم حذف الصنف بنجاح', 'success'); } /* PRODUCT PICKER */ const filteredProductsForSelector = useMemo(() => { const q = pickerSearchQuery.toLowerCase(); return products.filter(p => p.name.toLowerCase().includes(q) || p.sku.toLowerCase().includes(q) || p.id.toString().includes(q) ); }, [products, pickerSearchQuery]); function handleAdjustPickerQty(productId, adj) { setPickerSelections(prev => { const cur = prev[productId] || 0, next = cur + adj; if (next <= 0) { const c={...prev}; delete c[productId]; return c; } return {...prev, [productId]: next}; }); } function handleSetPickerQtyDirectly(productId, qty) { setPickerSelections(prev => { const q = Math.max(0, qty); if (q===0) { const c={...prev}; delete c[productId]; return c; } return {...prev, [productId]: q}; }); } function handleAddSelectedProductsToOrder() { const orderId = productPickerOrderId; if (!orderId) return; const sel = Object.entries(pickerSelections).filter(([,qty])=>qty>0); if (!sel.length) { showToast('⚠️ يرجى تحديد كمية للمنتجات أولاً!','error'); return; } pushToHistory(orders); const newItems = sel.map(([pIdStr, qty]) => { const p = products.find(x=>x.id===parseInt(pIdStr)); return { id: Date.now()+Math.floor(Math.random()*100000), productId:p.id, name:p.name, price:p.price, qty, isPrepared:false, discountValue:0, discountType:'fixed' }; }); saveStateToStorage(orders.map(o => o.id!==orderId ? o : {...o, items:[...o.items, ...newItems]})); setPickerSelections({}); setPickerSearchQuery(''); setProductPickerOrderId(null); showToast('➕ تم إضافة ' + newItems.length + ' منتج/منتجات بنجاح للطلب #' + orderId + '!', 'success'); } /* ORDER MATH */ function calculateOrderSums(order) { let subtotal=0, itemsDiscount=0; order.items.forEach(item => { const sub = item.price * item.qty; const disc = item.discountType==='percent' ? sub*(item.discountValue/100) : item.discountValue*item.qty; subtotal += sub; itemsDiscount += Math.min(disc, sub); }); return { subtotal, itemsDiscount, finalTotal: Math.max(0, subtotal-itemsDiscount+order.shippingTotal-order.discountTotal) }; } /* ORDER SELECTION */ function handleToggleSelectOrder(id) { setSelectedOrderIds(prev => prev.includes(id) ? prev.filter(x=>x!==id) : [...prev, id]); } function handleToggleSelectAllOrders() { setSelectedOrderIds(selectedOrderIds.length===filteredOrders.length ? [] : filteredOrders.map(o=>o.id)); } /* WHATSAPP */ function handleNormalizePhone(phone) { let c = phone.replace(/\D/g,''); if (!c) return ''; if (c.startsWith('00')) c=c.slice(2); if (c.charAt(0)==='0') c='20'+c.slice(1); return c; } function generateWhatsAppMessage(order) { const { subtotal, itemsDiscount, finalTotal } = calculateOrderSums(order); const fullname = (order.customerName+' '+(order.customerLastName||'')).trim(); let t = '🛍️ *تفاصيل الفاتورة والطلب من EKP Store*\n'; t += '━━━━━━━━━━━━━━━━━━━━\n'; t += '🏷️ *رقم الطلب:* #'+order.id+'\n'; t += '👤 *العميل:* '+fullname+'\n'; t += '📞 *الهاتف:* '+order.phone+'\n'; if (order.phone2) t += '📞 *هاتف بديل:* '+order.phone2+'\n'; if (order.city || order.address1) t += '📍 *العنوان:* '+[order.address1,order.address2,order.city].filter(Boolean).join(' - ')+'\n'; t += '📅 *تاريخ الطلب:* '+order.date+'\n'; t += '📊 *الحالة الحالية:* '+(statusTranslations[order.status]||order.status)+'\n'; t += '━━━━━━━━━━━━━━━━━━━━\n📦 *المنتجات المطلوبة:*\n'; order.items.forEach((item,i) => { const s=item.price*item.qty, d=item.discountType==='percent'?s*(item.discountValue/100):item.discountValue*item.qty; t += (i+1)+'. *'+item.name+'*\n الكمية: '+item.qty+' × '+item.price+' ج.م\n'; if (d>0) t+=' خصم الصنف: -'+d+' ج.م\n'; t+=' الإجمالي: '+(s-d)+' ج.م\n'; }); t += '━━━━━━━━━━━━━━━━━━━━\n'; t += '💵 *المجموع الفرعي:* '+subtotal+' ج.م\n'; if (itemsDiscount>0) t+='🏷️ *خصم المنتجات:* -'+itemsDiscount+' ج.م\n'; if (order.discountTotal>0) t+='🎁 *خصم إضافي:* -'+order.discountTotal+' ج.م\n'; t += '🚚 *تكلفة الشحن لـ ['+order.city+']:* +'+order.shippingTotal+' ج.م\n'; t += '💰 *الإجمالي النهائي:* '+finalTotal+' ج.م\n'; if (order.trackingNumber) { t += '━━━━━━━━━━━━━━━━━━━━\n'; t += '📦 *رقم تتبع شحنتك:* '+order.trackingNumber+'\n'; t += '🔗 *رابط التتبع:* https://www.4tracking.net/en/tjax/track?nums='+encodeURIComponent(order.trackingNumber)+'\n'; } if (order.customerNote) { t += '━━━━━━━━━━━━━━━━━━━━\n📝 *ملاحظتك:* '+order.customerNote+'\n'; } t += '━━━━━━━━━━━━━━━━━━━━\n💡 *شكراً لتعاملك معنا!*'; return encodeURIComponent(t); } function handleOpenWhatsAppModal(order, e) { e.stopPropagation(); setWaModalOrder(order); setWaCustomPhone(handleNormalizePhone(order.phone)); } function handleSendWhatsApp() { if (!waModalOrder) return; const phone = waCustomPhone.replace(/\D/g,''); if (!phone || phone.length<10) { showToast('⚠️ يرجى كتابة رقم هاتف صحيح مسبوقاً بكود الدولة','error'); return; } window.open('https://wa.me/'+phone+'?text='+generateWhatsAppMessage(waModalOrder), '_blank', 'noreferrer,noopener'); setWaModalOrder(null); } /* SHIPPING CALC */ function handleGovernorateChange(orderId, gov) { pushToHistory(orders); let s = 50; if (['القاهرة','الجيزة','القليوبية'].includes(gov)) s=40; else if (['الإسكندرية','البحيرة','الإسماعيلية','السويس','بورسعيد','الشرقية','الغربية','الدقهلية','المنوفية','دمياط','كفر الشيخ'].includes(gov)) s=50; else if (['الفيوم','بني سويف','المنيا','أسيوط','سوهاج','قنا','الأقصر','أسوان'].includes(gov)) s=70; else s=85; saveStateToStorage(orders.map(o => o.id!==orderId ? o : {...o, city:gov, shippingTotal:s})); showToast('🚚 تم تحديث المحافظة وتسعيرة الشحن: '+s+' ج.م', 'info'); } /* NEW ORDER */ function handleCreateNewOrder(e) { e.preventDefault(); if (!newOrderForm.customerName) { showToast('⚠️ الاسم الأول للعميل مطلوب!','error'); return; } pushToHistory(orders); const newId = Math.floor(1000+Math.random()*9000); const now = new Date(); const date = now.toISOString().split('T')[0]+' '+now.toTimeString().slice(0,5); const newOrder = { id:newId, customerName:newOrderForm.customerName, customerLastName:newOrderForm.customerLastName, email:newOrderForm.email||(newOrderForm.phone||newId)+'@customer.com', phone:newOrderForm.phone||'—', phone2:newOrderForm.phone2, address1:newOrderForm.address1||'عنوان مجهول', address2:newOrderForm.address2, city:newOrderForm.city, date, status:'pending', paymentMethod:newOrderForm.paymentMethod, shippingTotal:Number(newOrderForm.shippingTotal), discountTotal:0, trackingNumber:'', customerNote:newOrderForm.customerNote, items:[], }; const updated = [newOrder, ...orders]; saveStateToStorage(updated); setShowOrderCreator(false); showToast('✨ تم إطلاق الطلب الجديد #'+newId+'! يمكنك الآن إضافة المنتجات.', 'success'); setExpandedOrderId(newId); setProductPickerOrderId(newId); setNewOrderForm({ customerName:'', customerLastName:'', email:'', phone:'', phone2:'', address1:'', address2:'', city:'القاهرة', paymentMethod:'الدفع عند الاستلام (COD)', shippingTotal:50, customerNote:'' }); } /* CSV EXPORT */ function handleExportCSV() { try { let csv = '\uFEFF'; csv += 'رقم الطلب,اسم العميل,البريد الإلكتروني,الهاتف,الهاتف البديل,العنوان الرئيسي,المحافظة,التاريخ,الحالة,طريقة الدفع,تكلفة الشحن,خصم الأوردر,رقم التتبع,ملاحظات,عدد العناصر,إجمالي الطلب\n'; filteredOrders.forEach(o => { const { finalTotal } = calculateOrderSums(o); const name = '"'+(o.customerName+' '+(o.customerLastName||'')).trim()+'"'; csv += [o.id, name, '"'+(o.email||'')+'"', '"'+(o.phone||'')+'"', '"'+(o.phone2||'')+'"', '"'+(o.address1||'')+'"', '"'+(o.city||'')+'"', '"'+(o.date||'')+'"', '"'+(statusTranslations[o.status]||o.status)+'"', '"'+(o.paymentMethod||'')+'"', o.shippingTotal, o.discountTotal, '"'+(o.trackingNumber||'')+'"', '"'+(o.customerNote||'')+'"', o.items.length, finalTotal ].join(',')+'\n'; }); const blob = new Blob([csv], {type:'text/csv;charset=utf-8;'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'EKP-Orders-'+new Date().toISOString().slice(0,10)+'.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); showToast('📥 تم تصدير ملف CSV بنجاح!', 'success'); } catch(e) { showToast('❌ فشل تصدير الملف.','error'); } } /* ─── RENDER ─────────────────────────────────────────────────────────── */ return h('div', { className:'min-h-screen bg-[#f1f5f9] text-slate-800 flex flex-col geometric-grid', dir:'rtl' }, /* HEADER */ h('header', { className:'h-16 bg-white border-b border-slate-200 sticky top-0 z-40 px-4 md:px-6 flex items-center shadow-sm' }, h('div', { className:'max-w-7xl w-full mx-auto flex flex-wrap items-center justify-between gap-3' }, /* Logo */ h('div', { className:'flex items-center gap-3' }, h('div', { className:'bg-indigo-600 p-2.5 rounded-lg text-white' }, h(ShoppingBag, { className:'w-5 h-5' }) ), h('div', null, h('div', { className:'flex items-center gap-2' }, h('h1', { className:'text-base md:text-lg font-bold text-slate-800' }, 'نظام إدارة الطلبات المتطور'), h('span', { className:'text-xs bg-indigo-50 text-indigo-700 font-bold px-2 py-0.5 rounded border border-indigo-100' }, 'v3.0') ) ) ), /* Actions */ h('div', { className:'flex items-center flex-wrap gap-2' }, h('div', { className:'flex items-center gap-2 text-sm text-slate-500 font-medium' }, h('span', { className:'live-indicator '+(isSavingLive?'saving':'') }), h('span', { className:'text-xs' }, isSavingLive ? 'جاري الحفظ...' : 'تم الحفظ تلقائياً ('+lastSavedTime+')') ), historyStack.length > 0 && h('button', { onClick: handleUndo, className:'bg-amber-50 hover:bg-amber-100 border border-amber-200 text-amber-800 px-3 py-1.5 rounded-lg flex items-center gap-1.5 font-bold text-xs' }, h(Undo,{className:'w-4 h-4'}), 'تراجع ('+historyStack.length+')'), h('button', { onClick:()=>setShowOrderCreator(true), className:'bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg font-medium shadow-sm flex items-center gap-2 text-sm' }, h(Plus,{className:'w-4 h-4'}), 'طلب جديد'), h('button', { onClick: handleExportCSV, className:'px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-sm font-medium rounded-lg flex items-center gap-2 shadow-sm' }, h(FileSpreadsheet,{className:'w-4 h-4'}), 'CSV') ) ) ), /* MAIN */ h('main', { className:'flex-1 max-w-7xl w-full mx-auto p-4 md:py-6 flex flex-col gap-5' }, /* STATS */ h('section', { className:'grid grid-cols-2 lg:grid-cols-5 gap-3' }, statCard({ label:'إجمالي الإيرادات', value: stats.totalSales.toLocaleString('en-US')+' ج.م', sub:'الطلبات المكتملة', icon:TrendingUp, accent:'emerald' }), statCard({ label:'إجمالي الطلبات', value: stats.totalOrders+' طلبات', sub:'قاعدة البيانات كاملة', icon:Package, accent:'slate' }), statCard({ label:'قيد التقديم', value: stats.pendingOrders+' أوردر', sub:'بحاجة للشحن', icon:Clock, accent:'amber' }), statCard({ label:'مكتملة', value: stats.completedOrders+' طلب', sub:'تم التسليم', icon:CheckCircle2, accent:'emerald' }), h('div', { className:'bg-white border border-slate-200 p-4 rounded-xl flex flex-col justify-between col-span-2 lg:col-span-1 shadow-sm' }, h('div', { className:'flex items-center justify-between text-slate-500' }, h('span', { className:'text-xs font-semibold' }, 'شحنات معلقة'), h(Truck, { className:'w-4 h-4 text-blue-500' }) ), h('div', { className:'mt-2.5' }, h('p', { className:'text-xl font-black text-blue-600 font-mono' }, stats.holdOrders+' '), h('span', { className:'text-xs text-slate-400' }, 'أوردرات'), h('div', { className:'text-[10px] text-blue-600 font-bold mt-1' }, 'بانتظار مراجعة الإدارة') ) ) ), /* FILTERS */ h('section', { className:'bg-white border border-slate-200 p-4 rounded-xl flex flex-col md:flex-row gap-3 items-stretch md:items-center shadow-sm' }, /* Search */ h('div', { className:'relative flex-1' }, h(Search, { className:'absolute right-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400' }), h('input', { type:'text', placeholder:'ابحث برقم الأوردر، اسم العميل، التليفون، الإيميل أو رقم التتبع...', value:searchQuery, onChange:e=>setSearchQuery(e.target.value), className:'w-full pl-4 pr-10 py-2 bg-slate-50 border border-slate-200 focus:border-indigo-500 rounded-lg text-sm outline-none' }), searchQuery && h('button', { onClick:()=>setSearchQuery(''), className:'absolute left-3 top-1/2 -translate-y-1/2 bg-slate-200 hover:bg-slate-300 text-slate-600 p-1 rounded-md' }, h(X,{className:'w-3 h-3'})) ), /* Status filter */ h('div', { className:'flex items-center gap-1.5 bg-slate-50 border border-slate-200 px-3 py-1.5 rounded-lg text-xs text-slate-600' }, h(Filter, { className:'w-3.5 h-3.5 text-indigo-500' }), h('span', { className:'font-bold' }, 'حالة:'), h('select', { value:statusFilter, onChange:e=>setStatusFilter(e.target.value), className:'bg-transparent text-slate-800 font-bold outline-none cursor-pointer' }, h('option',{value:'all'},'الكل'), ...Object.entries(statusTranslations).map(([k,v])=>h('option',{key:k,value:k},v)) ) ), /* Gov filter */ h('div', { className:'flex items-center gap-1.5 bg-slate-50 border border-slate-200 px-3 py-1.5 rounded-lg text-xs text-slate-600' }, h(MapPin, { className:'w-3.5 h-3.5 text-indigo-500' }), h('span', { className:'font-bold' }, 'المحافظة:'), h('select', { value:governorateFilter, onChange:e=>setGovernorateFilter(e.target.value), className:'bg-transparent text-slate-800 font-bold outline-none cursor-pointer' }, h('option',{value:'all'},'كل مصر'), ...Object.keys(EGYPTIAN_GOVERNORATES).map(g=>h('option',{key:g,value:g},g)) ) ), (statusFilter!=='all'||governorateFilter!=='all'||searchQuery) && h('button', { onClick:()=>{ setStatusFilter('all'); setGovernorateFilter('all'); setSearchQuery(''); }, className:'text-xs bg-red-50 hover:bg-red-100 border border-red-200 text-red-600 px-3 py-1.5 rounded-lg font-bold' }, 'إعادة الفلاتر') ), /* ORDERS TABLE */ h('section', { className:'bg-white border border-slate-200 rounded-xl overflow-hidden shadow-sm' }, h('div', { className:'overflow-x-auto' }, h('table', { className:'w-full text-right border-collapse' }, h('thead', null, h('tr', { className:'bg-slate-50 border-b border-slate-200 text-slate-500 text-xs font-bold' }, h('th',{className:'py-4 px-4 w-12 text-center'}, h('input',{ type:'checkbox', checked:filteredOrders.length>0&&selectedOrderIds.length===filteredOrders.length, onChange:handleToggleSelectAllOrders, className:'rounded border-slate-300 text-indigo-600 w-4 h-4 cursor-pointer' }) ), h('th',{className:'py-4 px-4'},'رقم الطلب'), h('th',{className:'py-4 px-4'},'العميل'), h('th',{className:'py-4 px-4'},'المحافظة والعنوان'), h('th',{className:'py-4 px-4'},'التاريخ'), h('th',{className:'py-4 px-4 text-center'},'العناصر'), h('th',{className:'py-4 px-4 text-left'},'قيمة الفاتورة'), h('th',{className:'py-4 px-4 text-center'},'رقم التتبع'), h('th',{className:'py-4 px-4 text-center'},'الحالة'), h('th',{className:'py-4 px-4 text-center'},'إجراءات') ) ), h('tbody', { className:'divide-y divide-slate-100 text-slate-700 text-sm' }, filteredOrders.length === 0 ? h('tr', null, h('td', { colSpan:10, className:'py-16 text-center text-slate-500' }, h('div', { className:'flex flex-col items-center gap-2' }, h(Package, { className:'w-10 h-10 text-slate-300 mb-2' }), h('p', { className:'font-bold text-slate-600' }, 'لم نعثر على أي أوردرات!'), h('p', { className:'text-xs text-slate-400' }, 'جرب تعديل الفلاتر أو أنشئ طلباً جديداً.') ) )) : filteredOrders.map(order => { const isExpanded = expandedOrderId === order.id; const { finalTotal } = calculateOrderSums(order); const fullname = (order.customerName+' '+(order.customerLastName||'')).trim(); const numItems = order.items.reduce((s,i)=>s+i.qty, 0); return [ /* PRIMARY ROW */ h('tr', { key:'row-'+order.id, onClick:()=>setExpandedOrderId(isExpanded?null:order.id), className:'hover:bg-slate-50 transition-all duration-150 cursor-pointer '+(isExpanded?'bg-indigo-50/30 border-r-4 border-indigo-600':'') }, h('td',{className:'py-4 px-4 text-center', onClick:e=>e.stopPropagation()}, h('input',{type:'checkbox', checked:selectedOrderIds.includes(order.id), onChange:()=>handleToggleSelectOrder(order.id), className:'rounded border-slate-300 text-indigo-600 w-4 h-4 cursor-pointer'}) ), h('td',{className:'py-4 px-4'}, h('span',{className:'font-mono font-bold text-indigo-600 bg-indigo-50 px-2.5 py-1 rounded-lg border border-indigo-100'},'#'+order.id) ), h('td',{className:'py-4 px-4'}, h('div',{className:'font-bold text-slate-800 text-sm'},fullname), h('div',{className:'text-xs text-slate-500 flex items-center gap-1 mt-1'}, h(Phone,{className:'w-3 h-3 text-emerald-600'}), h('span',{className:'font-mono'},order.phone) ) ), h('td',{className:'py-4 px-4 max-w-xs'}, h('span',{className:'text-xs font-bold bg-slate-100 text-slate-700 border border-slate-200 px-2 py-0.5 rounded-md'},order.city), h('p',{className:'text-xs text-slate-500 truncate mt-1'},order.address1) ), h('td',{className:'py-4 px-4'}, h('div',{className:'text-xs text-slate-600 font-mono flex items-center gap-1.5'}, h(CalendarIcon,{className:'w-3.5 h-3.5 text-slate-400'}), order.date ) ), h('td',{className:'py-4 px-4 text-center'}, h('span',{className:'bg-slate-100 text-slate-700 border border-slate-200 text-xs px-2.5 py-1 rounded-full'},numItems+' عناصر') ), h('td',{className:'py-4 px-4 text-left font-mono font-bold text-slate-800'}, finalTotal.toLocaleString('en-US')+' ج.م' ), h('td',{className:'py-4 px-4 text-center', onClick:e=>e.stopPropagation()}, order.trackingNumber ? h('div',{className:'inline-flex items-center gap-1 bg-slate-50 border border-slate-200 pl-1 pr-2.5 py-1 rounded-lg'}, h('span',{className:'text-xs font-mono text-slate-600'},order.trackingNumber), h('button',{ onClick:e=>handleCopyTracking(order.trackingNumber,e), className:'copy-pulse p-1.5 rounded transition '+(copiedTracking===order.trackingNumber?'bg-emerald-500 text-white':'hover:bg-slate-100 text-slate-500') }, copiedTracking===order.trackingNumber ? h(Check,{className:'w-3 h-3'}) : h(Copy,{className:'w-3 h-3'})) ) : h('span',{className:'text-xs text-slate-400 italic'},'— لا توجد شحنة') ), h('td',{className:'py-4 px-4 text-center'}, h('span',{className:'px-2.5 py-1 rounded-full text-[11px] font-bold inline-block whitespace-nowrap shadow-sm border '+(statusColorClasses[order.status]||'')}, statusTranslations[order.status]||order.status ) ), h('td',{className:'py-4 px-4 text-center', onClick:e=>e.stopPropagation()}, h('div',{className:'flex items-center justify-center gap-1.5'}, h('button',{onClick:e=>handleOpenWhatsAppModal(order,e), className:'p-2 bg-emerald-50 hover:bg-emerald-100 border border-emerald-200 text-emerald-700 rounded-lg duration-150', title:'مشاركة عبر واتساب'}, h(Share2,{className:'w-4 h-4'}) ), h('button',{onClick:()=>setExpandedOrderId(isExpanded?null:order.id), className:'p-2 bg-slate-100 hover:bg-slate-200 border border-slate-200 rounded-lg duration-150 text-slate-700'}, isExpanded ? h(ChevronUp,{className:'w-4 h-4'}) : h(ChevronDown,{className:'w-4 h-4'}) ) ) ) ), /* EXPANDED ROW */ isExpanded && h('tr', { key:'exp-'+order.id }, h('td', { colSpan:10, className:'bg-slate-50/80 py-5 px-5 border-b border-slate-200' }, h('div', { className:'grid grid-cols-1 lg:grid-cols-12 gap-5' }, /* Left – Customer Fields */ h('div', { className:'lg:col-span-4 space-y-3' }, h('h3',{className:'text-sm font-bold text-indigo-600 border-b border-slate-200 pb-2 flex items-center gap-1.5'}, h(User,{className:'w-4 h-4'}), 'بيانات العميل والشحن (تُحفظ حياً)' ), /* name */ h('div',{className:'grid grid-cols-2 gap-2'}, formField('الاسم الأول', order.customerName, v=>handleOrderFieldChange(order.id,'customerName',v)), formField('اللقب / العائلة', order.customerLastName||'', v=>handleOrderFieldChange(order.id,'customerLastName',v)) ), h('div',{className:'grid grid-cols-2 gap-2'}, formField('الهاتف الأساسي', order.phone, v=>handleOrderFieldChange(order.id,'phone',v), 'font-mono'), formField('الهاتف البديل', order.phone2||'', v=>handleOrderFieldChange(order.id,'phone2',v), 'font-mono') ), formField('البريد الإلكتروني', order.email||'', v=>handleOrderFieldChange(order.id,'email',v)), h('div',null, h('label',{className:'text-[10px] text-indigo-600 font-bold block mb-1'},'📍 تحديد المحافظة:'), h('select',{ value:order.city, onChange:e=>handleGovernorateChange(order.id,e.target.value), className:'w-full bg-white border border-slate-200 px-2.5 py-1.5 rounded-lg text-xs text-slate-800 font-bold cursor-pointer outline-none focus:border-indigo-500' }, ...Object.keys(EGYPTIAN_GOVERNORATES).map(g=>h('option',{key:g,value:g},g))) ), formField('العنوان بالتفصيل', order.address1, v=>handleOrderFieldChange(order.id,'address1',v)), formField('رقم المبنى / الشقة', order.address2||'', v=>handleOrderFieldChange(order.id,'address2',v)), h('div',null, h('label',{className:'text-[10px] text-slate-500 font-bold block mb-1'},'تحديث حالة الأوردر:'), h('select',{ value:order.status, onChange:e=>handleOrderFieldChange(order.id,'status',e.target.value), className:'w-full bg-white border border-slate-200 px-3 py-1.5 rounded-lg text-xs text-slate-800 font-bold outline-none focus:border-indigo-500' }, ...Object.entries(statusTranslations).map(([k,v])=>h('option',{key:k,value:k},v))) ), /* Financial */ h('div',{className:'bg-white border border-slate-200 rounded-lg p-3 space-y-2'}, h('h4',{className:'text-xs font-bold text-slate-600 mb-2 flex items-center gap-1'},h(FileText,{className:'w-3.5 h-3.5'}), 'ملخص الفاتورة'), ...(()=>{ const {subtotal, itemsDiscount, finalTotal} = calculateOrderSums(order); return [ finRow('المجموع الفرعي', subtotal+' ج.م', 'text-slate-700'), itemsDiscount>0 && finRow('خصم المنتجات', '-'+itemsDiscount+' ج.م', 'text-rose-600'), order.discountTotal>0 && finRow('خصم الأوردر', '-'+order.discountTotal+' ج.م', 'text-rose-600'), finRow('الشحن لـ '+order.city, '+'+order.shippingTotal+' ج.م', 'text-blue-600'), h('div',{className:'border-t border-slate-100 pt-2 flex justify-between font-black text-sm text-slate-900'}, h('span',null,'الإجمالي النهائي'), h('span',{className:'font-mono text-indigo-700'},finalTotal.toLocaleString('en-US')+' ج.م') ), ].filter(Boolean); })() ) ), /* Right – Items */ h('div', { className:'lg:col-span-8 space-y-3' }, h('div',{className:'flex flex-wrap items-center justify-between gap-2 border-b border-slate-200 pb-2'}, h('h3',{className:'text-sm font-bold text-indigo-600 flex items-center gap-1.5'},h(Package,{className:'w-4 h-4'}), 'منتجات الطلب'), h('div',{className:'flex flex-wrap gap-2'}, h('button',{onClick:()=>setProductPickerOrderId(order.id), className:'px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-bold rounded-lg flex items-center gap-1'}, h(Plus,{className:'w-3.5 h-3.5'}), 'إضافة أصناف' ), (selectedItemIdsInOrder[order.id]||[]).length > 0 && h('button',{ onClick:()=>handleDeleteSelectedItems(order.id), className:'px-3 py-1.5 bg-red-50 hover:bg-red-100 border border-red-200 text-red-600 text-xs font-bold rounded-lg flex items-center gap-1' }, h(Trash2,{className:'w-3.5 h-3.5'}), 'حذف المحدد ('+((selectedItemIdsInOrder[order.id]||[]).length)+')') ) ), order.items.length === 0 ? h('div',{className:'py-10 text-center text-slate-400 text-sm border-2 border-dashed border-slate-200 rounded-xl'}, h(Package,{className:'w-8 h-8 mx-auto mb-2 text-slate-300'}), h('p',null,'لا يوجد منتجات في هذا الطلب'), h('p',{className:'text-xs mt-1'},'اضغط إضافة أصناف لإضافة المنتجات') ) : h('div',{className:'space-y-2'}, /* Select all */ h('div',{className:'flex items-center gap-2 text-xs text-slate-500 pb-1'}, h('input',{type:'checkbox', checked:(selectedItemIdsInOrder[order.id]||[]).length===order.items.length&&order.items.length>0, onChange:e=>handleSelectAllItems(order.id,order.items,e.target.checked), className:'rounded border-slate-300 text-indigo-600 w-3.5 h-3.5 cursor-pointer'}), h('span',null,'تحديد الكل') ), ...order.items.map(item => { const isChecked = (selectedItemIdsInOrder[order.id]||[]).includes(item.id); const sub = item.price*item.qty; const disc = item.discountType==='percent'?sub*(item.discountValue/100):item.discountValue*item.qty; const net = sub - disc; return h('div',{key:item.id, className:'bg-white border border-slate-200 rounded-lg p-3'}, h('div',{className:'flex items-start gap-2'}, h('input',{type:'checkbox', checked:isChecked, onChange:()=>handleToggleItemSelect(order.id,item.id), className:'mt-1 rounded border-slate-300 text-indigo-600 w-4 h-4 cursor-pointer'}), h('div',{className:'flex-1 space-y-2'}, h('div',{className:'flex flex-wrap items-center justify-between gap-2'}, h('div',{className:'flex items-center gap-2'}, h('input',{type:'checkbox', checked:item.isPrepared, onChange:e=>handleToggleItemPrepared(order.id,item.id,e.target.checked), className:'rounded border-slate-300 accent-emerald-600 w-4 h-4 cursor-pointer', title:'وضع علامة جاهز'}), h('p',{className:'text-xs font-bold text-slate-800'},item.name), item.isPrepared && h('span',{className:'text-[10px] bg-emerald-50 text-emerald-700 border border-emerald-100 px-1.5 py-0.5 rounded font-bold'},'✓ جاهز') ), h('button',{onClick:()=>handleUnsolicitedDeleteItem(order.id,item.id), className:'p-1 text-slate-300 hover:text-red-500 hover:bg-red-50 rounded transition', title:'حذف هذا الصنف'}, h(X,{className:'w-3.5 h-3.5'}) ) ), h('div',{className:'flex flex-wrap items-center gap-3'}, /* Qty */ h('div',{className:'flex items-center gap-1'}, h('span',{className:'text-[10px] text-slate-500 font-bold'},'الكمية:'), h('div',{className:'flex items-center bg-slate-50 border border-slate-200 rounded overflow-hidden'}, h('button',{onClick:()=>handleItemQtyPriceChange(order.id,item.id,{qty:item.qty-1}), className:'px-1.5 py-1 hover:bg-slate-200 text-slate-500 text-xs font-bold'},'-'), h('input',{type:'number',value:item.qty,onChange:e=>handleItemQtyPriceChange(order.id,item.id,{qty:parseInt(e.target.value)||1}), className:'w-8 text-center text-xs font-mono font-bold bg-transparent outline-none'}), h('button',{onClick:()=>handleItemQtyPriceChange(order.id,item.id,{qty:item.qty+1}), className:'px-1.5 py-1 hover:bg-slate-200 text-slate-500 text-xs font-bold'},'+') ) ), /* Price */ h('div',{className:'flex items-center gap-1'}, h('span',{className:'text-[10px] text-slate-500 font-bold'},'السعر:'), h('input',{type:'number',value:item.price,onChange:e=>handleItemQtyPriceChange(order.id,item.id,{price:parseFloat(e.target.value)||0}), className:'w-16 text-xs font-mono bg-slate-50 border border-slate-200 rounded px-2 py-1 text-center outline-none focus:border-indigo-500'}) ), /* Discount */ h('div',{className:'flex items-center gap-1'}, h('span',{className:'text-[10px] text-slate-500 font-bold'},'خصم:'), h('input',{type:'number',value:item.discountValue,min:0,onChange:e=>handleItemDiscountChange(order.id,item.id,parseFloat(e.target.value)||0,item.discountType), className:'w-14 text-xs font-mono bg-slate-50 border border-slate-200 rounded px-2 py-1 text-center outline-none focus:border-indigo-500'}), h('select',{value:item.discountType,onChange:e=>handleItemDiscountChange(order.id,item.id,item.discountValue,e.target.value), className:'text-[10px] bg-slate-50 border border-slate-200 rounded px-1 py-1 outline-none cursor-pointer'}, h('option',{value:'fixed'},'ج.م'), h('option',{value:'percent'},'%') ) ), /* Net */ h('div',{className:'mr-auto text-xs font-mono font-black text-emerald-700'}, net.toLocaleString('en-US')+' ج.م' ) ) ) ) ); }) ), /* Extra order-level fields */ h('div',{className:'grid grid-cols-2 gap-3 mt-2'}, h('div',null, h('label',{className:'text-[10px] text-slate-500 font-bold block mb-1'},'رقم التتبع:'), h('input',{type:'text',value:order.trackingNumber,onChange:e=>handleOrderFieldChange(order.id,'trackingNumber',e.target.value), className:'w-full bg-white border border-slate-200 px-3 py-1.5 rounded-lg text-xs font-mono outline-none focus:border-indigo-500'}) ), h('div',null, h('label',{className:'text-[10px] text-slate-500 font-bold block mb-1'},'طريقة الدفع:'), h('select',{value:order.paymentMethod,onChange:e=>handleOrderFieldChange(order.id,'paymentMethod',e.target.value), className:'w-full bg-white border border-slate-200 px-3 py-1.5 rounded-lg text-xs outline-none'}, h('option',{value:'الدفع عند الاستلام (COD)'},'الدفع عند الاستلام'), h('option',{value:'فودافون كاش'},'فودافون كاش'), h('option',{value:'بطاقة ائتمانية (فوري)'},'بطاقة ائتمانية') ) ) ), h('div',null, h('label',{className:'text-[10px] text-slate-500 font-bold block mb-1'},'ملاحظات العميل:'), h('textarea',{value:order.customerNote||'',onChange:e=>handleOrderFieldChange(order.id,'customerNote',e.target.value), className:'w-full bg-white border border-slate-200 p-2.5 text-xs rounded-lg outline-none resize-none h-16 focus:border-indigo-500', placeholder:'أي ملاحظات للتوصيل أو الشحن...'}) ) ) ) ) ) ]; }) ) ) ) ) ), /* BULK BAR */ selectedOrderIds.length > 0 && h('div', { className:'fixed bottom-0 right-0 left-0 z-30 bg-slate-900 border-t border-slate-700 py-3 px-6' }, h('div', { className:'max-w-7xl mx-auto flex flex-wrap items-center justify-between gap-3' }, h('div',{className:'flex items-center gap-2'}, h('div',{className:'p-2 bg-blue-600 text-white rounded-lg'}, h(SlidersHorizontal,{className:'w-4 h-4'})), h('span',{className:'text-sm font-black text-white'},'تحرير جماعي لـ ('+selectedOrderIds.length+') طلبات') ), h('div',{className:'flex items-center gap-2'}, h('div',{className:'flex items-center gap-1.5 bg-slate-800 border border-slate-700 rounded-xl px-3 py-2 text-xs'}, h('span',{className:'text-slate-400 font-bold'},'تعديل الحالة:'), h('select',{value:bulkStatusToApply,onChange:e=>setBulkStatusToApply(e.target.value), className:'bg-transparent text-white font-extrabold cursor-pointer outline-none'}, h('option',{value:''},'--- اختر ---'), ...Object.entries(statusTranslations).map(([k,v])=>h('option',{key:k,value:k,className:'bg-slate-950'},v)) ) ), h('button',{ disabled:!bulkStatusToApply, onClick:()=>{ if(bulkStatusToApply){handleBulkStatusChange(bulkStatusToApply);setBulkStatusToApply('');} }, className:'px-4 py-2 rounded-xl text-xs font-black '+(bulkStatusToApply?'bg-blue-600 hover:bg-blue-500 text-white cursor-pointer':'bg-slate-800 text-slate-500 cursor-not-allowed') },'تحديث المحدد'), h('button',{onClick:()=>setSelectedOrderIds([]), className:'px-3 py-1.5 bg-slate-800 text-slate-300 text-xs font-bold rounded-xl hover:bg-slate-700'},'إلغاء التحديد') ) ) ), /* PRODUCT PICKER MODAL */ productPickerOrderId !== null && h('div', { className:'fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-sm' }, h('div', { className:'bg-slate-900 border border-slate-800 w-full max-w-2xl rounded-2xl overflow-hidden shadow-2xl flex flex-col max-h-[85vh]' }, h('div',{className:'p-4 bg-slate-950/60 border-b border-slate-800 flex items-center justify-between'}, h('div',{className:'flex items-center gap-2'}, h('div',{className:'p-2 bg-indigo-600 rounded-lg text-white'},h(Plus,{className:'w-5 h-5'})), h('div',null, h('h3',{className:'text-md font-extrabold text-white'},'إضافة منتجات للطلب #'+productPickerOrderId), h('p',{className:'text-[10px] text-slate-400 mt-0.5'},'ابحث واختر المنتجات مع الكمية') ) ), h('button',{onClick:()=>{setProductPickerOrderId(null);setPickerSelections({});setPickerSearchQuery('');}, className:'p-1 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg'},h(X,{className:'w-4 h-4'})) ), h('div',{className:'p-4 border-b border-slate-800/80'}, h('div',{className:'relative'}, h(Search,{className:'absolute right-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400'}), h('input',{type:'text',placeholder:'ابحث باسم المنتج أو الـ SKU...',value:pickerSearchQuery, onChange:e=>setPickerSearchQuery(e.target.value), className:'w-full pl-4 pr-10 py-2 bg-slate-950 border border-slate-800 rounded-xl text-xs outline-none focus:border-blue-600'}) ) ), h('div',{className:'flex-1 overflow-y-auto p-4 space-y-2'}, filteredProductsForSelector.length === 0 ? h('div',{className:'py-12 text-center text-slate-500 text-xs'},'⚠️ لم يتم العثور على منتجات مطابقة!') : filteredProductsForSelector.map(p=>{ const qty = pickerSelections[p.id]||0; return h('div',{key:p.id, className:'flex items-center justify-between gap-3 p-2.5 rounded-xl border '+(qty>0?'bg-indigo-950/25 border-indigo-500/40':'bg-slate-950/40 border-slate-800 hover:border-slate-700')}, h('div',{className:'flex items-center gap-3'}, h('input',{type:'checkbox',checked:qty>0,onChange:e=>e.target.checked?handleAdjustPickerQty(p.id,1):handleSetPickerQtyDirectly(p.id,0), className:'rounded text-indigo-600 w-4 h-4 cursor-pointer accent-indigo-600'}), h('div',null, h('p',{className:'text-xs font-extrabold text-white'},p.name), h('div',{className:'flex items-center gap-1.5 mt-0.5'}, h('span',{className:'text-[9px] font-mono bg-slate-800 text-slate-400 px-1.5 rounded'},'SKU: '+p.sku), h('span',{className:'text-[9px] text-slate-400'},'مخزون: ',h('strong',{className:p.stock&&p.stock<10?'text-rose-400':'text-slate-300'},p.stock||'غير محدود')) ) ) ), h('div',{className:'flex items-center gap-3'}, h('span',{className:'text-xs font-mono font-bold text-emerald-400'},p.price+' ج.م'), h('div',{className:'flex items-center bg-slate-900 border border-slate-700 rounded overflow-hidden'}, h('button',{onClick:()=>handleAdjustPickerQty(p.id,-1),className:'px-2 py-1 hover:bg-slate-800 text-slate-400 hover:text-white font-bold text-sm'},'-'), h('input',{type:'number',value:qty,onChange:e=>handleSetPickerQtyDirectly(p.id,parseInt(e.target.value)||0), className:'w-10 bg-transparent text-center font-mono font-bold text-xs text-white focus:outline-none'}), h('button',{onClick:()=>handleAdjustPickerQty(p.id,1),className:'px-2 py-1 hover:bg-slate-800 text-slate-400 hover:text-white font-bold text-sm'},'+') ) ) ); }) ), h('div',{className:'p-4 bg-slate-950 border-t border-slate-800 flex items-center justify-between'}, h('div',{className:'text-xs text-slate-400'}, 'تم تحديد ',h('strong',{className:'text-indigo-400 font-mono'},Object.values(pickerSelections).filter(q=>q>0).length),' صنف | ', h('strong',{className:'text-indigo-400 font-mono'},Object.values(pickerSelections).reduce((a,b)=>a+b,0)),' قطعة' ), h('div',{className:'flex items-center gap-2'}, h('button',{onClick:()=>{setProductPickerOrderId(null);setPickerSelections({});setPickerSearchQuery('');}, className:'px-4 py-2 bg-slate-800 text-slate-300 text-xs font-bold rounded-xl hover:bg-slate-700'},'إلغاء'), h('button',{onClick:handleAddSelectedProductsToOrder, className:'px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white text-xs font-black rounded-xl cursor-pointer'},'✅ تأكيد الإضافة') ) ) ) ), /* WHATSAPP MODAL */ waModalOrder !== null && h('div', { className:'fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-sm' }, h('div', { className:'bg-slate-900 border border-slate-850 w-full max-w-xl rounded-2xl overflow-hidden shadow-2xl flex flex-col' }, h('div',{className:'p-4 bg-slate-950/60 border-b border-slate-800 flex items-center justify-between'}, h('div',{className:'flex items-center gap-2 text-emerald-400'}, h(Share2,{className:'w-5 h-5 text-emerald-400'}), h('h3',{className:'text-md font-extrabold text-white'},'إرسال الفاتورة عبر واتساب') ), h('button',{onClick:()=>setWaModalOrder(null), className:'p-1 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg'},h(X,{className:'w-4 h-4'})) ), h('div',{className:'p-4 space-y-4'}, h('div',null, h('label',{className:'text-xs text-slate-400 font-bold block mb-1'},'رقم تليفون المستلم (مع كود الدولة):'), h('input',{type:'text',value:waCustomPhone,onChange:e=>setWaCustomPhone(e.target.value), className:'w-full bg-slate-950 border border-slate-800 px-3 py-2.5 rounded-xl font-mono text-sm text-slate-200 outline-none focus:border-emerald-500'}), h('p',{className:'text-[9px] text-slate-500 mt-1'},'مثال: 201012345678') ), h('div',null, h('label',{className:'text-xs text-slate-400 font-bold block mb-1'},'معاينة الرسالة:'), h('div',{className:'bg-emerald-950/10 border border-emerald-900/30 text-emerald-100 rounded-xl p-4 text-xs font-mono whitespace-pre-wrap max-h-52 overflow-y-auto leading-relaxed'}, decodeURIComponent(generateWhatsAppMessage(waModalOrder)) ) ) ), h('div',{className:'p-4 bg-slate-950 border-t border-slate-800 flex items-center justify-end gap-2'}, h('button',{onClick:()=>setWaModalOrder(null), className:'px-4 py-2 bg-slate-800 text-slate-300 text-xs font-bold rounded-xl hover:bg-slate-700'},'إغلاق'), h('button',{onClick:handleSendWhatsApp, className:'px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-black rounded-xl flex items-center gap-1.5'}, h(Share2,{className:'w-4 h-4'}), 'افتح WhatsApp الآن' ) ) ) ), /* NEW ORDER MODAL */ showOrderCreator && h('div', { className:'fixed inset-0 z-50 flex items-center justify-center p-4 bg-slate-950/80 backdrop-blur-sm' }, h('div', { className:'bg-slate-900 border border-slate-800 w-full max-w-xl rounded-2xl overflow-hidden shadow-2xl flex flex-col' }, h('div',{className:'p-4 bg-slate-950/60 border-b border-slate-800 flex items-center justify-between'}, h('div',{className:'flex items-center gap-2'}, h('div',{className:'p-2 bg-blue-600 rounded-lg text-white'},h(Plus,{className:'w-5 h-5'})), h('div',null, h('h3',{className:'text-md font-extrabold text-white'},'إنشاء طلب جديد'), h('p',{className:'text-[10px] text-slate-400 mt-0.5'},'أدخل بيانات العميل وسيبدأ الطلب فوراً') ) ), h('button',{type:'button',onClick:()=>setShowOrderCreator(false), className:'p-1 bg-slate-800 hover:bg-slate-700 text-slate-300 rounded-lg'},h(X,{className:'w-4 h-4'})) ), h('div',{className:'p-5 space-y-4 max-h-[70vh] overflow-y-auto'}, h('div',{className:'grid grid-cols-2 gap-3'}, newOrderInput('الاسم الأول: *', newOrderForm.customerName, v=>setNewOrderForm({...newOrderForm,customerName:v}), 'text', true), newOrderInput('اللقب / الشهرة:', newOrderForm.customerLastName, v=>setNewOrderForm({...newOrderForm,customerLastName:v})) ), h('div',{className:'grid grid-cols-2 gap-3'}, newOrderInput('الهاتف الأساسي:', newOrderForm.phone, v=>setNewOrderForm({...newOrderForm,phone:v}), 'text', false, 'font-mono'), newOrderInput('تليفون بديل:', newOrderForm.phone2, v=>setNewOrderForm({...newOrderForm,phone2:v}), 'text', false, 'font-mono') ), h('div',null, h('label',{className:'text-xs text-slate-450 font-bold block mb-1'},'📍 المحافظة:'), h('select',{value:newOrderForm.city,onChange:e=>setNewOrderForm({...newOrderForm,city:e.target.value}), className:'w-full bg-slate-950 border border-slate-800 px-3 py-2 text-xs text-slate-200 font-bold rounded-xl cursor-pointer'}, ...Object.keys(EGYPTIAN_GOVERNORATES).map(g=>h('option',{key:g,value:g},g)) ) ), h('div',{className:'grid grid-cols-2 gap-3'}, newOrderInput('العنوان الأساسي:', newOrderForm.address1, v=>setNewOrderForm({...newOrderForm,address1:v})), newOrderInput('البريد الإلكتروني:', newOrderForm.email, v=>setNewOrderForm({...newOrderForm,email:v}), 'email') ), h('div',{className:'grid grid-cols-2 gap-3'}, h('div',null, h('label',{className:'text-xs text-slate-450 font-bold block mb-1'},'طريقة الدفع:'), h('select',{value:newOrderForm.paymentMethod,onChange:e=>setNewOrderForm({...newOrderForm,paymentMethod:e.target.value}), className:'w-full bg-slate-950 border border-slate-800 px-3 py-2 text-xs rounded-xl text-slate-200'}, h('option',{value:'الدفع عند الاستلام (COD)'},'الدفع عند الاستلام'), h('option',{value:'فودافون كاش'},'فودافون كاش'), h('option',{value:'بطاقة ائتمانية'},'بطاقة ائتمانية') ) ), newOrderInput('تكلفة الشحن (ج.م):', newOrderForm.shippingTotal, v=>setNewOrderForm({...newOrderForm,shippingTotal:parseInt(v)||0}), 'number') ), h('div',null, h('label',{className:'text-xs text-slate-450 font-bold block mb-1'},'ملاحظات التوصيل:'), h('textarea',{placeholder:'أي معلومات على بوليصة التوصيل...',value:newOrderForm.customerNote, onChange:e=>setNewOrderForm({...newOrderForm,customerNote:e.target.value}), className:'w-full bg-slate-950 border border-slate-800 p-3 text-xs rounded-xl text-slate-200 outline-none h-20 resize-none'}) ) ), h('div',{className:'p-4 bg-slate-950 border-t border-slate-800 flex items-center justify-end gap-2'}, h('button',{type:'button',onClick:()=>setShowOrderCreator(false), className:'px-4 py-2 bg-slate-800 text-slate-300 text-xs font-bold rounded-xl hover:bg-slate-700'},'إلغاء'), h('button',{type:'button',onClick:handleCreateNewOrder, className:'px-5 py-2 bg-blue-600 hover:bg-blue-500 text-white text-xs font-black rounded-xl cursor-pointer'},'✨ إنشاء الأوردر فوراً') ) ) ), /* TOAST */ toast && h('div', { className:'fixed top-4 left-4 z-50 p-4 rounded-2xl flex items-center gap-3 shadow-2xl border '+( toast.type==='error'?'bg-red-950 border-red-500/30 text-red-200': toast.type==='info'?'bg-indigo-950 border-indigo-500/30 text-indigo-200': 'bg-emerald-950 border-emerald-500/30 text-emerald-200' ) }, toast.type==='error' ? h(AlertCircle,{className:'w-5 h-5 text-red-400 shrink-0'}) : toast.type==='info' ? h(Info,{className:'w-5 h-5 text-indigo-400 shrink-0'}) : h(CheckCircle2,{className:'w-5 h-5 text-emerald-400 shrink-0'}), h('p',{className:'text-xs font-black leading-relaxed'},toast.message), h('button',{onClick:()=>setToast(null), className:'p-1 rounded-md text-slate-400 hover:text-white'},h(X,{className:'w-3.5 h-3.5'})) ) ); } /* ─── MINI HELPERS ───────────────────────────────────────────────────────── */ function statCard({ label, value, sub, icon: Icon, accent }) { const colors = { emerald:'emerald', amber:'amber', slate:'slate' }; const iconColors = { emerald:'text-emerald-500', amber:'text-amber-500', slate:'text-slate-500' }; const valColors = { emerald:'text-emerald-600', amber:'text-amber-600', slate:'text-slate-900' }; const subColors = { emerald:'text-emerald-600', amber:'text-amber-500', slate:'text-slate-400' }; return h('div', { className:'bg-white border border-slate-200 p-4 rounded-xl flex flex-col justify-between shadow-sm' }, h('div', { className:'flex items-center justify-between text-slate-500' }, h('span', { className:'text-xs font-semibold' }, label), h(Icon, { className:'w-4 h-4 '+(iconColors[accent]||'text-slate-500') }) ), h('div', { className:'mt-2.5' }, h('p', { className:'text-xl font-black font-mono '+(valColors[accent]||'text-slate-900') }, value), h('div', { className:'text-[10px] font-bold mt-1 '+(subColors[accent]||'text-slate-400') }, sub) ) ); } function formField(label, value, onChange, extraClass='') { return h('div', null, h('label', { className:'text-[10px] text-slate-500 font-bold block mb-1' }, label), h('input', { type:'text', value, onChange:e=>onChange(e.target.value), className:'w-full bg-white border border-slate-200 px-2.5 py-1.5 rounded-lg text-xs outline-none text-slate-800 focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition '+extraClass }) ); } function finRow(label, value, cls) { return h('div', { className:'flex justify-between text-xs '+cls }, h('span', { className:'text-slate-600' }, label), h('span', { className:'font-mono font-bold' }, value) ); } function newOrderInput(label, value, onChange, type='text', required=false, extraClass='') { return h('div', null, h('label', { className:'text-xs text-slate-450 font-bold block mb-1' }, label), h('input', { type, value, required, onChange:e=>onChange(e.target.value), className:'w-full bg-slate-950 border border-slate-800 px-3 py-2 text-xs rounded-xl text-slate-200 outline-none focus:border-blue-500 '+extraClass }) ); } /* ─── MOUNT ──────────────────────────────────────────────────────────────── */ const container = document.getElementById('ekp-orders-root'); if (container) { const root = ReactDOM.createRoot(container); root.render(React.createElement(App)); } }); // end waitForDeps })(); JS; } /* ───────────────────────────────────────────── 5. PREVENT WP FROM MANGLING RTL DIRECTION ──────────────────────────────────────────────── */ add_filter( 'body_class', function( $classes ) { if ( is_singular() || is_page() ) { // Don't remove existing classes — we handle RTL inside the widget only } return $classes; } );

0.96″ OLED Display Module (128×64) Complete Guide

High-Contrast Display Solution for Arduino and ESP Projects

Introduction

The 0.96″ OLED display module is a popular, low-power display solution featuring a 128×64 pixel resolution with crisp white, blue, or yellow pixels on a black background. These displays use SSD1306 or SH1106 drivers and communicate via I2C or SPI interfaces.

Key Features

Power Efficiency

Consumes only 0.04W during operation

👁️ High Contrast

16:1 contrast ratio for excellent readability

📱 Compact Size

27.3mm × 27.8mm module dimensions

💾 Built-in Memory

GDDRAM for display data storage

Technical Specifications

Display Type OLED (Organic LED)
Resolution 128 × 64 pixels
Driver IC SSD1306 or SH1106
Interface I2C (default) or SPI
Operating Voltage 3.3V – 5V
Viewing Angle >160°

Pin Configuration

Pin Description Arduino Connection
GND Ground GND
VCC Power (3.3V-5V) 3.3V or 5V
SCL I2C Clock A5 (Uno) or SCL
SDA I2C Data A4 (Uno) or SDA
Note: Some modules have additional pins for SPI interface (D/C, RST, CS)

Wiring with Arduino

I2C Connection

// Basic I2C Connections:
// OLED VCC → Arduino 5V
// OLED GND → Arduino GND
// OLED SCL → Arduino SCL (A5 on Uno)
// OLED SDA → Arduino SDA (A4 on Uno)

Finding I2C Address

Run this code to detect your OLED’s I2C address:

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("I2C Scanner");
}

void loop() {
  byte error, address;
  int devices = 0;
  
  Serial.println("Scanning...");
  
  for(address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
    
    if (error == 0) {
      Serial.print("Device found at 0x");
      if (address<16) Serial.print("0");
      Serial.println(address,HEX);
      devices++;
    }
  }
  
  if (devices == 0) Serial.println("No devices found");
  delay(5000);
}

 

Arduino Library Setup

  1. Install the Adafruit SSD1306 library from Library Manager 
  2. Install the Adafruit GFX library 
  3. Include these in your sketch:
Arduino
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

 

Basic Display Example

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Address 0x3C
  
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println("Hello, OLED!");
  display.display();
  delay(2000);
}

void loop() {
  // Show sensor data or animations here
}

 

Advanced Features

Custom Graphics

// Draw a rectangle
display.drawRect(10, 10, 50, 30, SSD1306_WHITE);

// Draw a filled circle
display.fillCircle(64, 32, 15, SSD1306_WHITE);

Text Formatting

display.setTextSize(2); // 2x size text
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 10);
display.println("Large Text");

display.setTextSize(1);
display.println("Normal Text");

Bitmap Images

// Use LCD Assistant to convert images
static const unsigned char PROGMEM logo[] = {
  // Bitmap data here
};

display.drawBitmap(0, 0, logo, 128, 64, 1);

Animation

for(int i=0; i<128; i++) {
  display.clearDisplay();
  display.fillRect(i, 16, 10, 32, WHITE);
  display.display();
  delay(20);
}

Troubleshooting

Blank Display

  • Check I2C address (try both 0x3C and 0x3D)
  • Verify all connections are secure
  • Ensure contrast is set (display.ssd1306_command(SSD1306_SETCONTRAST))

Garbled Display

  • Check for proper voltage (3.3V-5V)
  • Add pull-up resistors (4.7kΩ) on SDA/SCL if needed
  • Reduce I2C clock speed if using long wires

Library Issues

  • Install latest Adafruit libraries
  • Modify library if using SH1106 driver
  • Check for conflicting libraries