feat(v1.6): inline status change on orders list
Phase 44 complete: - Clickable status badge opens dropdown with grouped statuses - AJAX POST changes status without page reload (optimistic update) - Fixed-position dropdown escapes table overflow:hidden - updateStatus() returns JSON for AJAX, redirect for standard POST Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -142,6 +142,8 @@ final class OrdersController
|
||||
],
|
||||
'stats' => $stats,
|
||||
'statusPanel' => $statusPanel,
|
||||
'allStatuses' => $this->buildAllStatusOptions($statusConfig),
|
||||
'statusColorMap' => $statusColorMap,
|
||||
'errorMessage' => (string) ($result['error'] ?? ''),
|
||||
], 'layouts/app');
|
||||
|
||||
@@ -241,19 +243,28 @@ final class OrdersController
|
||||
|
||||
public function updateStatus(Request $request): Response
|
||||
{
|
||||
$isAjax = strtolower($request->header('X-Requested-With')) === 'xmlhttprequest';
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
if ($orderId <= 0) {
|
||||
return Response::html('Not found', 404);
|
||||
return $isAjax
|
||||
? Response::json(['success' => false, 'error' => 'Not found'], 404)
|
||||
: Response::html('Not found', 404);
|
||||
}
|
||||
|
||||
$csrfToken = (string) $request->input('_token', '');
|
||||
if (!Csrf::validate($csrfToken)) {
|
||||
if ($isAjax) {
|
||||
return Response::json(['success' => false, 'error' => $this->translator->get('auth.errors.csrf_expired')], 403);
|
||||
}
|
||||
Flash::set('order.error', $this->translator->get('auth.errors.csrf_expired'));
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
|
||||
$newStatus = trim((string) $request->input('new_status', ''));
|
||||
if ($newStatus === '') {
|
||||
if ($isAjax) {
|
||||
return Response::json(['success' => false, 'error' => $this->translator->get('orders.details.status_change.status_required')], 422);
|
||||
}
|
||||
Flash::set('order.error', $this->translator->get('orders.details.status_change.status_required'));
|
||||
return Response::redirect('/orders/' . $orderId);
|
||||
}
|
||||
@@ -262,6 +273,23 @@ final class OrdersController
|
||||
$actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;
|
||||
|
||||
$success = $this->orders->updateOrderStatus($orderId, $newStatus, 'user', $actorName !== '' ? $actorName : null);
|
||||
|
||||
if ($isAjax) {
|
||||
if (!$success) {
|
||||
return Response::json(['success' => false, 'error' => $this->translator->get('orders.details.status_change.failed')], 500);
|
||||
}
|
||||
$statusConfig = $this->orders->statusPanelConfig();
|
||||
$statusLabelMap = $this->statusLabelMap($statusConfig);
|
||||
$statusColorMap = $this->statusColorMap($statusConfig);
|
||||
$normalizedCode = strtolower(trim($newStatus));
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'status_code' => $normalizedCode,
|
||||
'status_label' => $this->statusLabel($normalizedCode, $statusLabelMap),
|
||||
'status_color' => $statusColorMap[$normalizedCode] ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
Flash::set('order.success', $this->translator->get('orders.details.status_change.success'));
|
||||
} else {
|
||||
@@ -317,7 +345,7 @@ final class OrdersController
|
||||
. '<span>' . htmlspecialchars($buyerCity, ENT_QUOTES, 'UTF-8') . '</span>'
|
||||
. '</div>'
|
||||
. '</div>',
|
||||
'status_badges' => '<div class="orders-status-wrap">'
|
||||
'status_badges' => '<div class="orders-status-wrap" data-order-id="' . (int) ($row['id'] ?? 0) . '" data-current-status="' . htmlspecialchars($status, ENT_QUOTES, 'UTF-8') . '">'
|
||||
. $this->statusBadge($status, $this->statusLabel($status, $statusLabelMap), $statusColorMap[strtolower(trim($status))] ?? '')
|
||||
. '</div>',
|
||||
'products' => $this->productsHtml($itemsPreview, $itemsCount, $itemsQty),
|
||||
|
||||
Reference in New Issue
Block a user