From ef55689295ff5ab438ce433c07beae3a64839cec Mon Sep 17 00:00:00 2001 From: Liutenko Oleksandr Date: Tue, 22 Sep 2026 19:54:55 +0300 Subject: [PATCH] Add orders table mockup to post-login dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/pages/DashboardPage.css | 187 +++++++++++++++++++++++++-- frontend/src/pages/DashboardPage.tsx | 146 +++++++++++++++++++-- 2 files changed, 316 insertions(+), 17 deletions(-) diff --git a/frontend/src/pages/DashboardPage.css b/frontend/src/pages/DashboardPage.css index 055dfd4..6c62f96 100644 --- a/frontend/src/pages/DashboardPage.css +++ b/frontend/src/pages/DashboardPage.css @@ -51,21 +51,192 @@ color: var(--color-primary); } -.dashboard-body { +.orders-body { flex: 1; + padding: 24px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.orders-toolbar { display: flex; align-items: center; - justify-content: center; - padding: 24px; + justify-content: space-between; } -.dashboard-placeholder { - text-align: center; - max-width: 420px; +.orders-create-btn { + border: none; + 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); + 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); - 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; } diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 4f77a71..73f320e 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,3 +1,5 @@ +import { useMemo, useState } from 'react' + import '@/pages/DashboardPage.css' import { useAuth } from '@/features/auth/useAuth' @@ -7,12 +9,68 @@ const ROLE_LABEL: Record = { viewer: 'Наблюдатель', } +type OrderStatus = 'processing' | 'new' | 'delivered' + +const STATUS_LABEL: Record = { + 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() { const { user, logout } = useAuth() + const [selected, setSelected] = useState>(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 (
@@ -27,13 +85,83 @@ export function DashboardPage() {
-
-
-

Очередь заказов появится здесь

-

- Вход выполнен успешно. Экран «получено, чек не пробит» будет добавлен на следующих - этапах — после интеграций с Новой Поштой и Checkbox. -

+
+
+

Заказы

+ +
+ +
+ Выбрано: {selected.size} заказов + setSearch(e.target.value)} + /> + +
+ +
+ + + + + + + + + + + + + + + {filtered.map((order) => ( + + + + + + + + + + + ))} + {filtered.length === 0 && ( + + + + )} + +
+ + ID заказаНомер ТТНКлиентСуммаПредоплатаСтатусДействия
+ toggleOne(order.id)} + aria-label={`Выбрать заказ ${order.id}`} + /> + {order.id}{order.ttn}{order.client}{order.totalLabel} + + + + {STATUS_LABEL[order.status]} + + + +
+ Ничего не найдено +