# Admin Orders UX Improvements Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Добавить в списке заказов маркер `Цена не подтверждена` и заменить смену статуса в деталке на быстрые кнопки допустимых переходов.

**Architecture:** Изменения ограничены фронтендом (`client`) и опираются на текущие поля заказа (`status`, `deliveryType`, `deliveryFeeLocked`) и текущую логику переходов `getAdminNextOrderStatuses`. Серверные API и контракты не меняются, синхронизация данных остается через существующую инвалидацию React Query.

**Tech Stack:** React, TypeScript, MUI, TanStack React Query, Vitest, Testing Library.

---

## File Structure

**Create:**
- `client/src/shared/lib/order-requires-price-approval.ts`
- `client/src/shared/lib/__tests__/order-requires-price-approval.test.ts`
- `client/src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx`
- `client/src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx`

**Modify:**
- `client/src/pages/admin-orders/ui/AdminOrdersPage.tsx`
- `client/src/features/order-detail/ui/OrderDetailContent.tsx`

---

### Task 1: Вычисление признака "цена не подтверждена"

**Files:**
- Create: `client/src/shared/lib/order-requires-price-approval.ts`
- Test: `client/src/shared/lib/__tests__/order-requires-price-approval.test.ts`

- [ ] **Step 1: Write the failing unit test**

```ts
import { describe, expect, it } from 'vitest'
import { orderRequiresPriceApproval } from '../order-requires-price-approval'

describe('orderRequiresPriceApproval', () => {
  it('returns true for delivery pending payment with unlocked delivery fee', () => {
    expect(
      orderRequiresPriceApproval({
        status: 'PENDING_PAYMENT',
        deliveryType: 'delivery',
        deliveryFeeLocked: false,
      }),
    ).toBe(true)
  })

  it('returns false when delivery fee is already locked', () => {
    expect(
      orderRequiresPriceApproval({
        status: 'PENDING_PAYMENT',
        deliveryType: 'delivery',
        deliveryFeeLocked: true,
      }),
    ).toBe(false)
  })

  it('returns false for pickup even if payment is pending', () => {
    expect(
      orderRequiresPriceApproval({
        status: 'PENDING_PAYMENT',
        deliveryType: 'pickup',
        deliveryFeeLocked: false,
      }),
    ).toBe(false)
  })

  it('returns false for non-pending statuses', () => {
    expect(
      orderRequiresPriceApproval({
        status: 'PAID',
        deliveryType: 'delivery',
        deliveryFeeLocked: false,
      }),
    ).toBe(false)
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd client && npm test -- src/shared/lib/__tests__/order-requires-price-approval.test.ts`  
Expected: FAIL with module/function not found.

- [ ] **Step 3: Write minimal implementation**

```ts
type PriceApprovalOrder = {
  status: string
  deliveryType: 'delivery' | 'pickup'
  deliveryFeeLocked: boolean
}

export function orderRequiresPriceApproval(order: PriceApprovalOrder): boolean {
  return order.status === 'PENDING_PAYMENT' && order.deliveryType === 'delivery' && order.deliveryFeeLocked === false
}
```

- [ ] **Step 4: Run test to verify it passes**

Run: `cd client && npm test -- src/shared/lib/__tests__/order-requires-price-approval.test.ts`  
Expected: PASS (4 tests).

- [ ] **Step 5: Commit**

```bash
git add client/src/shared/lib/order-requires-price-approval.ts client/src/shared/lib/__tests__/order-requires-price-approval.test.ts
git commit -m "test: add price approval predicate for admin orders"
```

---

### Task 2: Маркер "Цена не подтверждена" в списке заказов

**Files:**
- Modify: `client/src/pages/admin-orders/ui/AdminOrdersPage.tsx`
- Test: `client/src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx`

- [ ] **Step 1: Write the failing component test for chip visibility**

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { AdminOrdersPage } from '../AdminOrdersPage'

const fetchAdminOrdersMock = vi.fn()

vi.mock('@/entities/order/api/admin-order-api', () => ({
  fetchAdminOrders: fetchAdminOrdersMock,
  fetchAdminOrder: vi.fn(),
}))

describe('AdminOrdersPage price approval marker', () => {
  it('shows "Цена не подтверждена" for eligible order', async () => {
    fetchAdminOrdersMock.mockResolvedValueOnce({
      items: [
        {
          id: 'order-1',
          status: 'PENDING_PAYMENT',
          deliveryType: 'delivery',
          deliveryFeeLocked: false,
          totalCents: 10000,
          currency: 'RUB',
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString(),
          user: { id: 'u1', email: 'a@example.com' },
          itemsCount: 1,
        },
      ],
      total: 1,
      page: 1,
      pageSize: 20,
    })

    const qc = new QueryClient()
    render(
      <QueryClientProvider client={qc}>
        <AdminOrdersPage />
      </QueryClientProvider>,
    )

    expect(await screen.findByText('Цена не подтверждена')).toBeInTheDocument()
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd client && npm test -- src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx`  
Expected: FAIL because chip text is not rendered yet.

- [ ] **Step 3: Implement marker in `AdminOrdersPage`**

```tsx
import Chip from '@mui/material/Chip'
import { orderRequiresPriceApproval } from '@/shared/lib/order-requires-price-approval'

// ...

{group.items.map((o) => {
  const needsPriceApproval = orderRequiresPriceApproval({
    status: o.status,
    deliveryType: o.deliveryType,
    deliveryFeeLocked: o.deliveryFeeLocked,
  })

  return (
    <TableRow key={o.id} hover>
      <TableCell>
        <Stack direction="row" spacing={1} alignItems="center">
          <Box component="span">{o.id.slice(-8)}</Box>
          {needsPriceApproval && <Chip size="small" color="warning" label="Цена не подтверждена" />}
        </Stack>
      </TableCell>
      {/* ... */}
    </TableRow>
  )
})}
```

- [ ] **Step 4: Add negative case and rerun test**

```tsx
it('does not show marker for non-eligible order', async () => {
  fetchAdminOrdersMock.mockResolvedValueOnce({
    items: [
      {
        id: 'order-2',
        status: 'PENDING_PAYMENT',
        deliveryType: 'delivery',
        deliveryFeeLocked: true,
        totalCents: 10000,
        currency: 'RUB',
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString(),
        user: { id: 'u2', email: 'b@example.com' },
        itemsCount: 2,
      },
    ],
    total: 1,
    page: 1,
    pageSize: 20,
  })

  const qc = new QueryClient()
  render(
    <QueryClientProvider client={qc}>
      <AdminOrdersPage />
    </QueryClientProvider>,
  )

  expect(await screen.findByText('order-2'.slice(-8))).toBeInTheDocument()
  expect(screen.queryByText('Цена не подтверждена')).not.toBeInTheDocument()
})
```

Run: `cd client && npm test -- src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx`  
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add client/src/pages/admin-orders/ui/AdminOrdersPage.tsx client/src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx
git commit -m "feat: show price approval marker in admin orders list"
```

---

### Task 3: Быстрые кнопки смены статуса в деталке

**Files:**
- Modify: `client/src/features/order-detail/ui/OrderDetailContent.tsx`
- Test: `client/src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx`

- [ ] **Step 1: Write failing tests for quick actions**

```tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { OrderDetailContent } from '../OrderDetailContent'
import type { AdminOrderDetailResponse } from '@/entities/order/api/admin-order-api'

const setAdminOrderStatusMock = vi.fn(async () => undefined)

vi.mock('@/entities/order/api/admin-order-api', async () => {
  const actual = await vi.importActual<object>('@/entities/order/api/admin-order-api')
  return {
    ...actual,
    setAdminOrderStatus: setAdminOrderStatusMock,
    postAdminOrderMessage: vi.fn(async () => undefined),
  }
})

function buildDetail(patch: Partial<AdminOrderDetailResponse['item']>): AdminOrderDetailResponse['item'] {
  return {
    id: 'o1',
    status: 'PENDING_PAYMENT',
    deliveryType: 'delivery',
    deliveryCarrier: null,
    paymentMethod: 'online',
    itemsSubtotalCents: 10000,
    deliveryFeeCents: 500,
    deliveryFeeLocked: false,
    totalCents: 10500,
    currency: 'RUB',
    addressSnapshotJson: null,
    comment: null,
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    user: {
      id: 'u1',
      email: 'a@example.com',
      displayName: null,
      avatar: null,
      avatarStyle: null,
    },
    items: [],
    messages: [],
    ...patch,
  }
}

describe('OrderDetailContent quick status actions', () => {
  it('renders quick action buttons for next statuses', () => {
    const qc = new QueryClient()
    render(
      <QueryClientProvider client={qc}>
        <OrderDetailContent detail={buildDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' })} orderId="o1" />
      </QueryClientProvider>,
    )

    expect(screen.getByRole('button', { name: /Оплачен/i })).toBeInTheDocument()
  })

  it('calls setAdminOrderStatus on click', () => {
    const qc = new QueryClient()
    render(
      <QueryClientProvider client={qc}>
        <OrderDetailContent detail={buildDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' })} orderId="o1" />
      </QueryClientProvider>,
    )

    fireEvent.click(screen.getByRole('button', { name: /Оплачен/i }))
    expect(setAdminOrderStatusMock).toHaveBeenCalledWith('o1', 'PAID')
  })
})
```

- [ ] **Step 2: Run test to verify it fails**

Run: `cd client && npm test -- src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx`  
Expected: FAIL because old `Select` UI is still used.

- [ ] **Step 3: Replace select with quick action buttons**

```tsx
<Stack spacing={1}>
  <Typography variant="subtitle2" sx={{ fontWeight: 700 }}>
    Быстрый переход статуса
  </Typography>

  {nextStatuses.length === 0 ? (
    <Typography variant="body2" color="text.secondary">
      Статус финальный, смена недоступна
    </Typography>
  ) : (
    <Stack direction={{ xs: 'column', sm: 'row' }} spacing={1}>
      {nextStatuses.map((nextStatus) => {
        const isCancel = nextStatus === 'CANCELLED'
        return (
          <Button
            key={nextStatus}
            variant={isCancel ? 'outlined' : 'contained'}
            color={isCancel ? 'error' : 'primary'}
            onClick={() => statusMut.mutate(nextStatus)}
            disabled={statusMut.isPending}
          >
            {ORDER_STATUS_MAP[nextStatus] ?? nextStatus}
          </Button>
        )
      })}
    </Stack>
  )}
</Stack>
```

- [ ] **Step 4: Add pending/empty-state assertions and rerun tests**

```tsx
it('disables all quick action buttons while mutation is pending', () => {
  const qc = new QueryClient()
  setAdminOrderStatusMock.mockImplementationOnce(() => new Promise(() => {}))

  render(
    <QueryClientProvider client={qc}>
      <OrderDetailContent detail={buildDetail({ status: 'PENDING_PAYMENT', deliveryType: 'delivery' })} orderId="o1" />
    </QueryClientProvider>,
  )

  const paidButton = screen.getByRole('button', { name: /Оплачен/i })
  fireEvent.click(paidButton)
  expect(paidButton).toBeDisabled()
})

it('shows final state note when no transitions available', () => {
  const qc = new QueryClient()
  render(
    <QueryClientProvider client={qc}>
      <OrderDetailContent detail={buildDetail({ status: 'DONE', deliveryType: 'delivery' })} orderId="o1" />
    </QueryClientProvider>,
  )

  expect(screen.getByText('Статус финальный, смена недоступна')).toBeInTheDocument()
})
```

Run: `cd client && npm test -- src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx`  
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
git add client/src/features/order-detail/ui/OrderDetailContent.tsx client/src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx
git commit -m "feat: replace admin order status select with quick actions"
```

---

### Task 4: Регрессия, линт и финальная проверка

**Files:**
- Modify (if needed): `client/src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx`
- Modify (if needed): `client/src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx`
- Modify (if needed): `client/src/shared/lib/__tests__/order-requires-price-approval.test.ts`

- [ ] **Step 1: Run focused test suite for changed units**

Run:  
`cd client && npm test -- src/shared/lib/__tests__/order-requires-price-approval.test.ts src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx`  
Expected: PASS.

- [ ] **Step 2: Run frontend lint**

Run: `cd client && npm run lint`  
Expected: PASS with no new lint errors.

- [ ] **Step 3: Run format check**

Run: `cd client && npm run format:check`  
Expected: PASS, no formatting violations.

- [ ] **Step 4: Fix issues if any and re-run exact failed command**

Run (example): `cd client && npm run lint`  
Expected: PASS after fixes.

- [ ] **Step 5: Final commit**

```bash
git add client/src/pages/admin-orders/ui/AdminOrdersPage.tsx client/src/features/order-detail/ui/OrderDetailContent.tsx client/src/shared/lib/order-requires-price-approval.ts client/src/shared/lib/__tests__/order-requires-price-approval.test.ts client/src/pages/admin-orders/ui/__tests__/AdminOrdersPage.test.tsx client/src/features/order-detail/ui/__tests__/OrderDetailContent.test.tsx
git commit -m "feat: improve admin orders flow for price approval and status updates"
```
