Add orders table mockup to post-login dashboard
Static layout per design mock: order queue table with selection, search, prepayment inputs, status badges, and bulk-receipt button. Uses mock data only — no API wiring, per project rule to not build order features against a guessed CRM shape ahead of stage 3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -51,21 +51,192 @@
|
|||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-body {
|
.orders-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding: 24px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
padding: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-placeholder {
|
.orders-create-btn {
|
||||||
text-align: center;
|
border: none;
|
||||||
max-width: 420px;
|
background: var(--color-primary);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-create-btn:hover:not(:disabled) {
|
||||||
|
background: var(--color-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-create-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-filterbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-selected-count {
|
||||||
|
font-size: 13px;
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-placeholder h2 {
|
.orders-search {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 320px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-surface);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
margin-bottom: 8px;
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-filter-btn {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-filter-btn:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table-wrap {
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table th,
|
||||||
|
.orders-table td {
|
||||||
|
padding: 12px 14px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table th {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-table tbody tr:hover {
|
||||||
|
background: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-checkbox-col {
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-prepayment-input {
|
||||||
|
width: 64px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-status--processing {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #92400e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-status--new {
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-status--delivered {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #166534;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.orders-status--processing {
|
||||||
|
background: rgba(217, 119, 6, 0.18);
|
||||||
|
color: #fbbf24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-status--new {
|
||||||
|
background: rgba(59, 130, 246, 0.18);
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-status--delivered {
|
||||||
|
background: rgba(22, 163, 74, 0.18);
|
||||||
|
color: #86efac;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-receipt-btn {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-surface);
|
||||||
|
color: var(--color-text);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-receipt-btn:hover:not(:disabled) {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-receipt-btn:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orders-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
padding: 32px 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { useMemo, useState } from 'react'
|
||||||
|
|
||||||
import '@/pages/DashboardPage.css'
|
import '@/pages/DashboardPage.css'
|
||||||
import { useAuth } from '@/features/auth/useAuth'
|
import { useAuth } from '@/features/auth/useAuth'
|
||||||
|
|
||||||
@@ -7,12 +9,68 @@ const ROLE_LABEL: Record<string, string> = {
|
|||||||
viewer: 'Наблюдатель',
|
viewer: 'Наблюдатель',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OrderStatus = 'processing' | 'new' | 'delivered'
|
||||||
|
|
||||||
|
const STATUS_LABEL: Record<OrderStatus, string> = {
|
||||||
|
processing: 'Обработка',
|
||||||
|
new: 'Новый',
|
||||||
|
delivered: 'Доставлен',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderRow {
|
||||||
|
id: string
|
||||||
|
ttn: string
|
||||||
|
client: string
|
||||||
|
totalLabel: string
|
||||||
|
prepayment: string
|
||||||
|
status: OrderStatus
|
||||||
|
fiscalized: boolean
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Временная заглушка. Очередь «получено, чек не пробит» — этап 6 плана,
|
* Вёрстка макета из ТЗ — статичные данные, реальная очередь «доставлено, чек
|
||||||
* пока здесь только подтверждение, что вход и защищённый роут работают.
|
* не пробит» приедет на этапе 3+ вместе с CrmClient/StubCrmClient и заказами
|
||||||
|
* из БД. Пока не подключаем это к API, чтобы не гадать форму данных CRM.
|
||||||
*/
|
*/
|
||||||
|
const MOCK_ORDERS: OrderRow[] = [
|
||||||
|
{ id: '#ORD-24810', ttn: '20400123456', client: 'Иван Смирнов', totalLabel: '45 990 ₴', prepayment: '', status: 'processing', fiscalized: false },
|
||||||
|
{ id: '#ORD-24809', ttn: '20400123457', client: 'Ольга Ткаченко', totalLabel: '12 450 ₴', prepayment: '0%', status: 'new', fiscalized: false },
|
||||||
|
{ id: '#ORD-24808', ttn: '20400123458', client: 'Иван Смирнов', totalLabel: '45 990 ₴', prepayment: '15%', status: 'new', fiscalized: false },
|
||||||
|
{ id: '#ORD-24807', ttn: '20400123459', client: 'Марія Бondar', totalLabel: '8 200 ₴', prepayment: '20%', status: 'delivered', fiscalized: false },
|
||||||
|
{ id: '#ORD-24806', ttn: '20400123460', client: 'Петро Коваль', totalLabel: '31 000 ₴', prepayment: '0%', status: 'delivered', fiscalized: false },
|
||||||
|
{ id: '#ORD-24805', ttn: '20400123461', client: 'Іван Смирнов', totalLabel: '45 990 ₴', prepayment: '0%', status: 'delivered', fiscalized: false },
|
||||||
|
]
|
||||||
|
|
||||||
export function DashboardPage() {
|
export function DashboardPage() {
|
||||||
const { user, logout } = useAuth()
|
const { user, logout } = useAuth()
|
||||||
|
const [selected, setSelected] = useState<Set<string>>(new Set())
|
||||||
|
const [search, setSearch] = useState('')
|
||||||
|
|
||||||
|
const filtered = useMemo(() => {
|
||||||
|
const query = search.trim().toLowerCase()
|
||||||
|
if (!query) return MOCK_ORDERS
|
||||||
|
return MOCK_ORDERS.filter(
|
||||||
|
(order) =>
|
||||||
|
order.id.toLowerCase().includes(query) ||
|
||||||
|
order.ttn.includes(query) ||
|
||||||
|
order.client.toLowerCase().includes(query),
|
||||||
|
)
|
||||||
|
}, [search])
|
||||||
|
|
||||||
|
const allSelected = filtered.length > 0 && filtered.every((order) => selected.has(order.id))
|
||||||
|
|
||||||
|
function toggleAll() {
|
||||||
|
setSelected(allSelected ? new Set() : new Set(filtered.map((order) => order.id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleOne(id: string) {
|
||||||
|
setSelected((prev) => {
|
||||||
|
const next = new Set(prev)
|
||||||
|
if (next.has(id)) next.delete(id)
|
||||||
|
else next.add(id)
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-shell">
|
<div className="dashboard-shell">
|
||||||
@@ -27,13 +85,83 @@ export function DashboardPage() {
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="dashboard-body">
|
<main className="orders-body">
|
||||||
<div className="dashboard-placeholder">
|
<div className="orders-toolbar">
|
||||||
<h2>Очередь заказов появится здесь</h2>
|
<h2>Заказы</h2>
|
||||||
<p>
|
<button type="button" className="orders-create-btn" disabled={selected.size === 0}>
|
||||||
Вход выполнен успешно. Экран «получено, чек не пробит» будет добавлен на следующих
|
Создать чеки по выбранным ({selected.size})
|
||||||
этапах — после интеграций с Новой Поштой и Checkbox.
|
</button>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
|
<div className="orders-filterbar">
|
||||||
|
<span className="orders-selected-count">Выбрано: {selected.size} заказов</span>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
className="orders-search"
|
||||||
|
placeholder="Поиск по ID, ТТН, клиенту"
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button type="button" className="orders-filter-btn">
|
||||||
|
Фильтр
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="orders-table-wrap">
|
||||||
|
<table className="orders-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className="orders-checkbox-col">
|
||||||
|
<input type="checkbox" checked={allSelected} onChange={toggleAll} aria-label="Выбрать всё" />
|
||||||
|
</th>
|
||||||
|
<th>ID заказа</th>
|
||||||
|
<th>Номер ТТН</th>
|
||||||
|
<th>Клиент</th>
|
||||||
|
<th>Сумма</th>
|
||||||
|
<th>Предоплата</th>
|
||||||
|
<th>Статус</th>
|
||||||
|
<th>Действия</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{filtered.map((order) => (
|
||||||
|
<tr key={order.id}>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selected.has(order.id)}
|
||||||
|
onChange={() => toggleOne(order.id)}
|
||||||
|
aria-label={`Выбрать заказ ${order.id}`}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{order.id}</td>
|
||||||
|
<td>{order.ttn}</td>
|
||||||
|
<td>{order.client}</td>
|
||||||
|
<td>{order.totalLabel}</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" className="orders-prepayment-input" defaultValue={order.prepayment} placeholder="0%" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span className={`orders-status orders-status--${order.status}`}>
|
||||||
|
{STATUS_LABEL[order.status]}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" className="orders-receipt-btn" disabled={order.status !== 'delivered'}>
|
||||||
|
Чек
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
{filtered.length === 0 && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={8} className="orders-empty">
|
||||||
|
Ничего не найдено
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user