Щодня інтернет-магазин обробляє сотні замовлень. Клієнти телефонують у підтримку з питаннями «де моє замовлення?», «як мені повторити?». Без якісного особистого кабінету ці звернення стають основним джерелом витрат. В одному проєкті на 5000 замовлень на день ми побачили: 40% дзвінків — про статус та повтор. Після впровадження історії замовлень навантаження на підтримку впало на 30%, а повторні замовлення зросли на 20%. За 5 років ми реалізували такі рішення для 30+ магазинів на Laravel та React.
Чому історія замовлень критична для ecommerce?
Без неї користувач сліпий. Він не може перевірити статус, знайти чек або швидко замовити те ж саме. Наші клієнти після запуску фіксують зниження звернень на 20–30% та зростання LTV на 15–25% за рахунок зручного реордера. За даними внутрішньої аналітики, конверсія в повторну покупку у користувачів, які хоча б раз заходили в історію, в 2.5 рази вища.
Список замовлень з фільтрацією
Сторінка /account/orders — пагінований список з фільтрами. Для оптимізації використовуємо with() та latest(), щоб уникнути N+1 запитів:
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'), ]); } Кожен рядок містить номер замовлення, дату, кількість позицій, прев'ю перших трьох товарів, статус-бейдж, підсумкову суму та кнопки дій. Пошук за назвою товару реалізовано через orWhereHas — без дублювання запитів. Навіть при 100 000 замовлень сторінка завантажується за 200 мс.
Таблиця фільтрів:
| Фільтр | Тип | Приклад |
|---|---|---|
| Статус | Вибір зі списку | Доставлено |
| Період | Попередньо встановлені діапазони або кастом | Останні 30 днів |
| Пошук | Рядок введення | Замовлення №123 або телефон |
Детальна сторінка замовлення: що всередині?
Сторінка /account/orders/{id} збирає все необхідне в компонентному підході:
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> ); Кожен блок — перевикористовуваний компонент. Статус-таймлайн будується з окремої таблиці order_status_histories. При середній складності замовлення (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
Генерація через barryvdh/laravel-dompdf:
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% — економія близько 150 000 грн на місяць.
Нескінченна прокрутка vs пагінація: порівняння
| Характеристика | Infinite Scroll | Кнопкова пагінація |
|---|---|---|
| Залученість на мобільних | +20% переглядів | Базова |
| Розмір сторінки | Менше (10 записів) | Фіксований |
| Складність реалізації | Вища (Intersection Observer + кеш) | Нижча |
| Поведінка при поверненні | Зберігає скрол | Скидає на першу |
Infinite scroll на мобільних дає на 20% більше переглядів сторінок, ніж традиційна пагінація. Реалізуємо через Intersection Observer та useInfiniteQuery з keepPreviousData: true:
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]); Технічні деталі
Для infinite scroll важливо кешувати попередні сторінки, щоб при поверненні список не скидався. Ми використовуємо `keepPreviousData: true` в React Query та зберігаємо дані в глобальному стейті. На десктопі можна залишити звичайну пагінацію — користувачі звикли до кнопок.Наш підхід до кешування запитів знижує час завантаження списку замовлень у 2 рази порівняно з типовими рішеннями без кешу.
Зв'язок з відгуками та поверненнями
Прямо з історії замовлень доступні швидкі дії:
- «Залишити відгук» — з'являється через 3 дні після доставки
- «Оформити повернення» — активна протягом періоду повернення (14–30 днів)
- «Зв'язатися з підтримкою» — pre-fill форми з номером замовлення
Це скорочує шлях користувача та знижує кількість звернень з питанням «як мені повернути товар».
Що входить в роботу
- Аудит поточної реалізації особистого кабінету
- Проектування API (REST або GraphQL) та схеми бази даних
- Фронтенд-розробка на React/Next.js з використанням TypeScript
- Бекенд на Laravel з оптимізацією запитів
- Інтеграція з платіжними системами, трекінговими сервісами та CRM
- Документація API та інструкція для користувача
- Навчання команди замовника
- Гарантійна підтримка 3 місяці після запуску
Зв'яжіться з нами для оцінки вашого проєкту — ми підберемо оптимальне рішення під ваш стек та бюджет. Отримайте консультацію інженера: ми проаналізуємо поточну реалізацію та запропонуємо план інтеграції.







