בכל יום, חנות מקוונת מעבדת מאות הזמנות. לקוחות מתקשרים לתמיכה עם שאלות כמו "איפה ההזמנה שלי?" או "איך להזמין שוב?". ללא חשבון לקוח איכותי, פניות אלו הופכות למרכז עלות משמעותי. בפרויקט אחד עם 5,000 הזמנות ביום, ראינו 40% מהשיחות על סטטוס וחזרות. לאחר יישום היסטוריית הזמנות, עומס התמיכה ירד ב-30%, והזמנות חוזרות גדלו ב-20%. במשך 5 שנים, סיפקנו פתרונות כאלה ל-30+ חנויות על Laravel ו-React.
למה היסטוריית הזמנות קריטית למסחר אלקטרוני?
בלעדיה, המשתמש עיוור. הם לא יכולים לבדוק סטטוס, למצוא קבלה, או להזמין במהירות את אותם מוצרים. הלקוחות שלנו, לאחר ההשקה, מדווחים על ירידה של 20–30% בפניות ועלייה של 15–25% ב-LTV בזכות הזמנה חוזרת נוחה. לפי אנליטיקה פנימית, ההמרה לרכישה חוזרת בקרב משתמשים שביקרו בהיסטוריית הזמנות גבוהה פי 2.5.
רשימת הזמנות עם סינון
עמוד ה-/account/orders — רשימה מפולחת עם מסננים. לצורך אופטימיזציה, אנו משתמשים ב-with() וב-latest() כדי להימנע משאילתות N+1 (תיעוד Laravel):
public function index(Request $request): Response
{
$orders = $request->user()
->orders()
->with(['items.product.media', 'latestStatus'])
->when($request->status, fn($q, $s) => $q->where('status', $s))
->when($request->search, fn($q, $s) => $q->where('number', 'like', "%{$s}%"))
->latest()
->paginate(10);
return Inertia::render('Account/Orders/Index', [
'orders' => OrderListResource::collection($orders),
'statusOptions' => OrderStatus::labels(),
'filters' => $request->only('status', 'search'),
]);
}כל שורה מציגה מספר הזמנה, תאריך, מספר פריטים, תצוגה מקדימה של שלושת המוצרים הראשונים, תג סטטוס, סכום כולל, וכפתורי פעולה. חיפוש לפי שם מוצר מיושם באמצעות public function index(Request $request): Response { $orders = $request->user() ->orders() ->with(['items.product.media', 'latestStatus']) ->when($request->status, fn($q, $s) => $q->where('status', $s)) ->when($request->search, fn($q, $s) => $q->where('number', 'like', "%{$s}%") ) ->latest() ->paginate(10); return Inertia::render('Account/Orders/Index', [ 'orders' => OrderListResource::collection($orders), 'statusOptions' => OrderStatus::labels(), 'filters' => $request->only('status', 'search'), ]); } ללא שאילתות חוזרות. אפילו עם 100,000 הזמנות, העמוד נטען תוך 200 אלפיות השנייה.
| מסנן | סוג | דוגמה |
|---|---|---|
| סטטוס | רשימת בחירה | נמסר |
| תקופה | טווחים מוגדרים מראש או מותאמים אישית | 30 הימים האחרונים |
| חיפוש | שדה טקסט | הזמנה #123 או טלפון |
עמוד פרטים: מה יש בפנים?
עמוד ה-orWhereHas מרכיב הכל באמצעות גישת רכיבים:
const OrderDetailPage = ({ order }: { order: OrderDetail }) => (
<div className="space-y-6">
<OrderHeader order={order} /> {/* Номер, дата, статус */}
<StatusTimeline history={order.status_history} />
<OrderItemsTable items={order.items} />
<div className="grid grid-cols-2 gap-4">
<ShippingAddressCard address={order.shipping_address} />
<OrderSummaryCard order={order} /> {/* Subtotal, скидки, доставка, итого */}
</div>
{order.tracking_number && <ShippingTracker order={order} />}
<OrderActions order={order} /> {/* Повторить заказ, вернуть, скачать чек */}
</div>
);כל בלוק הוא רכיב לשימוש חוזר. ציר הזמן של הסטטוס בנוי מטבלת /account/orders/{id} נפרדת. עבור מורכבות הזמנה ממוצעת (3.5 פריטים), העמוד נטען תוך 150 אלפיות השנייה עם מטמון.
איך ליישם הזמנה חוזרת?
כפתור "הזמן שוב" מוסיף פריטים מההזמנה הישנה לעגלה הנוכחית עם בדיקת זמינות:
public function reorder(Order $order): JsonResponse
{
$this->authorize('view', $order);
$added = [];
$unavailable = [];
foreach ($order->items as $item) {
$product = Product::find($item->product_id);
if (!$product || !$product->is_active || $product->stock === 0) {
$unavailable[] = $item->product_name;
continue;
}
$this->cartService->add($product, min($item->quantity, $product->stock));
$added[] = $item->product_name;
}
return response()->json([
'added' => $added,
'unavailable' => $unavailable,
'cart_count' => $this->cartService->count(),
]);
}אם חלק מהפריטים לא זמינים, אנו מודיעים למשתמש אך מוסיפים את הזמינים. זה עדיף מאשר לחסום את כל ההזמנה. בבדיקות A/B, גישה זו נתנה +12% השלמה של הזמנות חוזרות.
הורדת חשבונית: יצירת PDF
יצירה באמצעות const OrderDetailPage = ({ order }: { order: OrderDetail }) => ( <div className="space-y-6"> <OrderHeader order={order} /> {/* Номер, дата, статус */} <StatusTimeline history={order.status_history} /> <OrderItemsTable items={order.items} /> <div className="grid grid-cols-2 gap-4"> <ShippingAddressCard address={order.shipping_address} /> <OrderSummaryCard order={order} /> {/* Subtotal, скидки, доставка, итого */} </div> {order.tracking_number && <ShippingTracker order={order} />} <OrderActions order={order} /> {/* Повторить заказ, вернуть, скачать чек */} </div> ); :
public function invoice(Order $order): Response
{
$this->authorize('view', $order);
$pdf = PDF::loadView('pdfs.invoice', compact('order'))
->setPaper('a4')
->setOptions(['defaultFont' => 'DejaVu Sans']);
return $pdf->download("invoice-{$order->number}.pdf");
}התבנית כוללת פרטי חנות, נתוני לקוח, טבלת פריטים, סיכומים, וקוד QR לאימות. ניתן להוסיף חתימה אלקטרונית לפי בקשה. עבור לקוח אחד, הכנסת הורדות חשבוניות הפחיתה את עומס העבודה החשבונאי ב-40%.
גלילה אינסופית לעומת פילוח: השוואה
| מאפיין | גלילה אינסופית | פילוח (כפתורים) |
|---|---|---|
| מעורבות בנייד | +20% צפיות בדפים | קו בסיס |
| גודל עמוד | קטן יותר (10 רשומות) | קבוע |
| מורכבות יישום | גבוהה יותר (Intersection Observer + מטמון) | נמוכה יותר |
| התנהגות חזרה | שומר על גלילה | מאפס לעמוד הראשון |
גלילה אינסופית בנייד מניבה 20% יותר צפיות בדפים מאשר פילוח מסורתי. מיושמת באמצעות order_status_histories ו-public function reorder(Order $order): JsonResponse { $this->authorize('view', $order); $added = []; $unavailable = []; foreach ($order->items as $item) { $product = Product::find($item->product_id); if (!$product || !$product->is_active || $product->stock === 0) { $unavailable[] = $item->product_name; continue; } $this->cartService->add($product, min($item->quantity, $product->stock)); $added[] = $item->product_name; } return response()->json([ 'added' => $added, 'unavailable' => $unavailable, 'cart_count' => $this->cartService->count(), ]); } עם barryvdh/laravel-dompdf:
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['orders', filters],
queryFn: ({ pageParam = 1 }) => api.get('/account/orders', { params: { page: pageParam, ...filters } }),
getNextPageParam: (last) => last.meta.current_page < last.meta.last_page ? last.meta.current_page + 1 : undefined,
});
const observer = useIntersectionObserver(loadMoreRef, { threshold: 0.5 });
useEffect(() => {
if (observer?.isIntersecting && hasNextPage) fetchNextPage();
}, [observer?.isIntersecting]);
פרטים טכניים
לגלילה אינסופית, חשוב לשמור במטמון דפים קודמים כדי שהרשימה לא תתאפס בחזרה. אנו משתמשים ב-`keepPreviousData: true` ב-React Query ומאחסנים נתונים במצב גלובלי. על שולחן העבודה, ניתן לשמור פילוח רגיל — משתמשים רגילים לכפתורים.גישת המטמון שלנו מפחיתה את זמן טעינת רשימת ההזמנות פי 2 בהשוואה לפתרונות טיפוסיים ללא מטמון.
אינטגרציה עם ביקורות והחזרות
ישירות מהיסטוריית ההזמנות, זמינות פעולות מהירות:
- "השאר ביקורת" — מופיע 3 ימים לאחר המשלוח
- "החזר" — פעיל בתוך תקופת ההחזרה (14–30 ימים)
- "צור קשר עם תמיכה" — ממלא מראש טופס עם מספר הזמנה
זה מקצר את מסע המשתמש ומפחית פניות על "איך להחזיר מוצר".
מה כלול בעבודה
- ביקורת על יישום חשבון הלקוח הנוכחי
- עיצוב API (REST או GraphQL) וסכימת מסד נתונים
- פיתוח צד לקוח ב-React/Next.js עם TypeScript
- צד שרת ב-Laravel עם אופטימיזציה של שאילתות
- אינטגרציה עם מערכות תשלום, שירותי מעקב ו-CRM
- תיעוד API ומדריך משתמש
- הכשרת צוות הלקוח
- תמיכה באחריות למשך 3 חודשים לאחר ההשקה
צור קשר כדי להעריך את הפרויקט שלך — נבחר את הפתרון האופטימלי עבור המחסנית והתקציב שלך. קבל ייעוץ מהנדס: ננתח את היישום הנוכחי שלך ונציע תוכנית אינטגרציה.







