Add refusals tab to the order dashboard

Orders whose Nova Poshta status is a refusal (102/103/105/108) now show
only under «Отказы», whether or not they have a receipt. GET /orders takes
tab=no_receipt|has_receipt|refused instead of has_receipt. NP statuses are
also polled for orders that already have a receipt, until NP reports a
final status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 21:25:59 +03:00
co-authored by Claude Opus 5.5
parent 56ac0fc370
commit f88b5cccc0
9 changed files with 116 additions and 33 deletions
+10 -10
View File
@@ -7,7 +7,7 @@ import { cancelReceipt, createReceipts } from '@/api/receipts'
import '@/pages/DashboardPage.css'
import { useAuth } from '@/features/auth/useAuth'
import { OrderDetailModal } from '@/features/orders/OrderDetailModal'
import type { Order } from '@/features/orders/types'
import type { Order, OrderTab } from '@/features/orders/types'
import { useOrders } from '@/features/orders/useOrders'
import { defaultPrepayment, prepaymentMatches, toKopecks } from '@/features/receipts/money'
import { CANCELLABLE, RECEIPT_STATUS } from '@/features/receipts/types'
@@ -19,11 +19,10 @@ const ROLE_LABEL: Record<string, string> = {
viewer: 'Наблюдатель',
}
type Tab = 'no_receipt' | 'has_receipt'
const TABS: { key: Tab; label: string }[] = [
const TABS: { key: OrderTab; label: string }[] = [
{ key: 'no_receipt', label: 'Без чека' },
{ key: 'has_receipt', label: 'Выписаны чеки' },
{ key: 'refused', label: 'Отказы' },
]
function npStatusTone(order: Order): 'delivered' | 'processing' | 'danger' | 'new' {
@@ -47,8 +46,8 @@ function paymentBadge(order: Order): { label: string; tone: 'delivered' | 'proce
export function DashboardPage() {
const { user, logout } = useAuth()
const [tab, setTab] = useState<Tab>('no_receipt')
const { data: orders, isLoading, isError } = useOrders(tab === 'has_receipt')
const [tab, setTab] = useState<OrderTab>('no_receipt')
const { data: orders, isLoading, isError } = useOrders(tab)
const queryClient = useQueryClient()
const [selected, setSelected] = useState<Set<string>>(new Set())
const [search, setSearch] = useState('')
@@ -103,7 +102,8 @@ export function DashboardPage() {
async function handleCancel(order: Order) {
if (!order.receipt_id) return
if (!window.confirm(`Отменить ЕТТН-чек по заказу ${order.id}? Заказ вернётся в очередь.`)) return
const outcome = tab === 'refused' ? 'Заказ останется в отказах.' : 'Заказ вернётся в очередь.'
if (!window.confirm(`Отменить ЕТТН-чек по заказу ${order.id}? ${outcome}`)) return
setCancellingId(order.id)
try {
await cancelReceipt(order.receipt_id)
@@ -336,7 +336,7 @@ export function DashboardPage() {
Чек
</button>
)}
{order.receipt_status && (tab === 'has_receipt' || !CANCELLABLE.has(order.receipt_status)) && (
{order.receipt_status && (tab !== 'no_receipt' || !CANCELLABLE.has(order.receipt_status)) && (
<span
className={`orders-status orders-status--${RECEIPT_STATUS[order.receipt_status].tone}`}
title={order.receipt_error ?? undefined}
@@ -344,7 +344,7 @@ export function DashboardPage() {
{RECEIPT_STATUS[order.receipt_status].label}
</span>
)}
{tab === 'has_receipt' && canFiscalize && order.receipt_status && CANCELLABLE.has(order.receipt_status) && (
{tab !== 'no_receipt' && canFiscalize && order.receipt_status && CANCELLABLE.has(order.receipt_status) && (
<button
type="button"
className="orders-delete-btn"
@@ -354,7 +354,7 @@ export function DashboardPage() {
Отменить
</button>
)}
{canDelete && tab === 'no_receipt' && (
{canDelete && (tab === 'no_receipt' || (tab === 'refused' && !order.has_receipt)) && (
<button
type="button"
className="orders-delete-btn"