feat: redesign Orders page with tabs for purchases/sales

- Add tabs: 'Pedidos Feitos' (compras) and 'Pedidos Recebidos' (vendas)
- Add stats bar with totals and pending count
- Add progress tracker for purchase orders
- Improved UI with icons and better styling
- Actions only visible on sales tab
This commit is contained in:
Tiago Yamamoto 2025-12-23 17:32:55 -03:00
parent e39ed59264
commit 8a5ec57e9c

View file

@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { Shell } from '../layouts/Shell'
import { apiClient } from '../services/apiClient'
import { formatCents } from '../utils/format'
import { useAuth } from '../context/AuthContext'
interface Order {
id: string
@ -13,25 +14,39 @@ interface Order {
}
const statusColors: Record<string, string> = {
Pendente: 'bg-yellow-100 text-yellow-800',
Pago: 'bg-blue-100 text-blue-800',
Faturado: 'bg-purple-100 text-purple-800',
Entregue: 'bg-green-100 text-green-800'
Pendente: 'bg-yellow-100 text-yellow-800 border-yellow-300',
Pago: 'bg-blue-100 text-blue-800 border-blue-300',
Faturado: 'bg-purple-100 text-purple-800 border-purple-300',
Entregue: 'bg-green-100 text-green-800 border-green-300'
}
const statusIcons: Record<string, string> = {
Pendente: '⏳',
Pago: '💳',
Faturado: '📄',
Entregue: '✅'
}
type TabType = 'compras' | 'vendas'
export function OrdersPage() {
const { user } = useAuth()
const [activeTab, setActiveTab] = useState<TabType>('compras')
const [orders, setOrders] = useState<Order[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
loadOrders()
}, [])
}, [activeTab])
const loadOrders = async () => {
try {
setLoading(true)
const response = await apiClient.get<{ orders: Order[]; total: number }>('/v1/orders')
const endpoint = activeTab === 'compras'
? '/v1/orders?role=buyer'
: '/v1/orders?role=seller'
const response = await apiClient.get<{ orders: Order[]; total: number }>(endpoint)
setOrders(response?.orders || [])
setError(null)
} catch (err) {
@ -53,85 +68,231 @@ export function OrdersPage() {
const formatCurrency = (cents: number | undefined | null) => formatCents(cents)
// Calculate stats
const pendingCount = orders.filter(o => o.status === 'Pendente').length
const totalValue = orders.reduce((sum, o) => sum + (o.total_cents || 0), 0)
return (
<Shell>
<div className="space-y-4">
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-xl font-semibold text-medicalBlue">Pedidos</h1>
<p className="text-sm text-gray-600">Gerenciamento de pedidos e status</p>
<h1 className="text-2xl font-bold text-gray-800">Gestão de Pedidos</h1>
<p className="text-gray-500">Acompanhe suas compras e vendas</p>
</div>
<button
onClick={loadOrders}
className="rounded bg-medicalBlue px-4 py-2 text-sm font-semibold text-white hover:opacity-90"
className="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-blue-700 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
</svg>
Atualizar
</button>
</div>
{loading && (
<div className="flex justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-medicalBlue"></div>
</div>
)}
{error && (
<div className="rounded bg-red-100 p-4 text-red-700">{error}</div>
)}
{!loading && orders.length === 0 && (
<div className="rounded bg-gray-100 p-8 text-center text-gray-600">
Nenhum pedido encontrado
</div>
)}
<div className="space-y-3">
{orders.map((order) => (
<div key={order.id} className="rounded-lg bg-white p-4 shadow-sm">
<div className="flex items-center justify-between">
<div>
<p className="font-semibold text-gray-800">Pedido #{order.id.slice(0, 8)}</p>
<p className="text-sm text-gray-500">
{new Date(order.created_at).toLocaleDateString('pt-BR')}
</p>
</div>
<div className="text-right">
<p className="text-lg font-bold text-medicalBlue">
{formatCurrency(order.total_cents)}
</p>
<span className={`inline-block rounded px-2 py-1 text-xs font-semibold ${statusColors[order.status] || 'bg-gray-100'}`}>
{order.status}
</span>
{/* Tabs */}
<div className="bg-white rounded-xl shadow-sm overflow-hidden">
<div className="flex border-b border-gray-200">
<button
onClick={() => setActiveTab('compras')}
className={`flex-1 py-4 px-6 text-center font-medium transition-all relative ${activeTab === 'compras'
? 'text-blue-600 bg-blue-50'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center justify-center gap-3">
<span className="text-2xl">🛒</span>
<div className="text-left">
<p className="font-semibold">Pedidos Feitos</p>
<p className="text-xs text-gray-400">Suas compras de outras farmácias</p>
</div>
</div>
<div className="mt-3 flex gap-2">
{order.status === 'Pendente' && (
<button
onClick={() => updateStatus(order.id, 'Pago')}
className="rounded bg-blue-500 px-3 py-1 text-xs text-white"
>
Marcar como Pago
</button>
)}
{order.status === 'Pago' && (
<button
onClick={() => updateStatus(order.id, 'Faturado')}
className="rounded bg-purple-500 px-3 py-1 text-xs text-white"
>
Faturar
</button>
)}
{order.status === 'Faturado' && (
<button
onClick={() => updateStatus(order.id, 'Entregue')}
className="rounded bg-green-500 px-3 py-1 text-xs text-white"
>
Marcar Entregue
</button>
)}
{activeTab === 'compras' && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600" />
)}
</button>
<button
onClick={() => setActiveTab('vendas')}
className={`flex-1 py-4 px-6 text-center font-medium transition-all relative ${activeTab === 'vendas'
? 'text-green-600 bg-green-50'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
}`}
>
<div className="flex items-center justify-center gap-3">
<span className="text-2xl">💰</span>
<div className="text-left">
<p className="font-semibold">Pedidos Recebidos</p>
<p className="text-xs text-gray-400">Vendas para outras farmácias</p>
</div>
</div>
{activeTab === 'vendas' && (
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-green-600" />
)}
</button>
</div>
{/* Stats Bar */}
<div className="flex items-center justify-between px-6 py-3 bg-gray-50 border-b border-gray-100">
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Total:</span>
<span className="font-bold text-gray-800">{orders.length} pedidos</span>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Valor:</span>
<span className="font-bold text-blue-600">{formatCurrency(totalValue)}</span>
</div>
{pendingCount > 0 && (
<div className="flex items-center gap-2 bg-yellow-100 px-3 py-1 rounded-full">
<span className="text-sm text-yellow-700">{pendingCount} pendentes</span>
</div>
)}
</div>
))}
</div>
{/* Content */}
<div className="p-6">
{loading && (
<div className="flex justify-center py-12">
<div className="animate-spin rounded-full h-10 w-10 border-b-2 border-blue-600"></div>
</div>
)}
{error && (
<div className="rounded-lg bg-red-50 border border-red-200 p-4 text-red-700 flex items-center gap-3">
<span className="text-xl"></span>
{error}
</div>
)}
{!loading && orders.length === 0 && (
<div className="text-center py-12">
<span className="text-5xl block mb-4">
{activeTab === 'compras' ? '🛒' : '💰'}
</span>
<h3 className="text-lg font-medium text-gray-700">
Nenhum pedido {activeTab === 'compras' ? 'feito' : 'recebido'} ainda
</h3>
<p className="text-gray-500 mt-1">
{activeTab === 'compras'
? 'Pesquise produtos e comece a comprar!'
: 'Cadastre produtos e aguarde as vendas!'
}
</p>
</div>
)}
{/* Orders List */}
<div className="space-y-4">
{orders.map((order) => (
<div
key={order.id}
className="rounded-xl border border-gray-200 bg-white p-5 hover:shadow-md transition-shadow"
>
<div className="flex items-start justify-between">
<div className="flex items-start gap-4">
<div className={`w-12 h-12 rounded-lg flex items-center justify-center text-2xl ${activeTab === 'compras' ? 'bg-blue-100' : 'bg-green-100'
}`}>
{statusIcons[order.status] || '📦'}
</div>
<div>
<p className="font-semibold text-gray-800">
Pedido #{order.id.slice(0, 8).toUpperCase()}
</p>
<p className="text-sm text-gray-500 mt-0.5">
{new Date(order.created_at).toLocaleDateString('pt-BR', {
day: '2-digit',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
</p>
</div>
</div>
<div className="text-right">
<p className="text-xl font-bold text-gray-800">
{formatCurrency(order.total_cents)}
</p>
<span className={`inline-flex items-center gap-1 rounded-full px-3 py-1 text-xs font-semibold border ${statusColors[order.status] || 'bg-gray-100'}`}>
{statusIcons[order.status]} {order.status}
</span>
</div>
</div>
{/* Actions - only for sales */}
{activeTab === 'vendas' && (
<div className="mt-4 pt-4 border-t border-gray-100 flex gap-2">
{order.status === 'Pendente' && (
<button
onClick={() => updateStatus(order.id, 'Pago')}
className="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 transition-colors"
>
💳 Confirmar Pagamento
</button>
)}
{order.status === 'Pago' && (
<button
onClick={() => updateStatus(order.id, 'Faturado')}
className="flex items-center gap-2 rounded-lg bg-purple-600 px-4 py-2 text-sm font-medium text-white hover:bg-purple-700 transition-colors"
>
📄 Faturar Pedido
</button>
)}
{order.status === 'Faturado' && (
<button
onClick={() => updateStatus(order.id, 'Entregue')}
className="flex items-center gap-2 rounded-lg bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 transition-colors"
>
Confirmar Entrega
</button>
)}
{order.status === 'Entregue' && (
<span className="text-green-600 text-sm font-medium flex items-center gap-2">
Pedido concluído com sucesso
</span>
)}
</div>
)}
{/* Track progress for purchases */}
{activeTab === 'compras' && (
<div className="mt-4 pt-4 border-t border-gray-100">
<div className="flex items-center justify-between">
{['Pendente', 'Pago', 'Faturado', 'Entregue'].map((status, idx) => {
const isCompleted = ['Pendente', 'Pago', 'Faturado', 'Entregue'].indexOf(order.status) >= idx
return (
<div key={status} className="flex items-center">
<div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold ${isCompleted
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-500'
}`}>
{isCompleted ? '✓' : idx + 1}
</div>
{idx < 3 && (
<div className={`w-16 h-1 mx-1 ${['Pendente', 'Pago', 'Faturado', 'Entregue'].indexOf(order.status) > idx
? 'bg-blue-600'
: 'bg-gray-200'
}`} />
)}
</div>
)
})}
</div>
<div className="flex justify-between mt-2 text-xs text-gray-500">
<span>Pendente</span>
<span>Pago</span>
<span>Faturado</span>
<span>Entregue</span>
</div>
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
</Shell>