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
- Install the Adafruit SSD1306 library from Library Manager
- Install the Adafruit GFX library
- 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
