321 lines
9.2 KiB
Python
321 lines
9.2 KiB
Python
from typing import Optional, Callable, Union
|
||
|
||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||
|
||
from config import PAGE_SIZE
|
||
from db.models import Category, Product
|
||
from dto.cart import CartItemDTO
|
||
from dto.catalogue import CatalogueView, CatalogueType
|
||
from misc.mapper import serialize_cat_id
|
||
|
||
|
||
def pagination_row(
|
||
*,
|
||
page: int,
|
||
prev_cb: Optional[str] = None,
|
||
next_cb: Optional[str] = None,
|
||
) -> list[InlineKeyboardButton]:
|
||
row = []
|
||
|
||
if not (prev_cb or next_cb):
|
||
return row
|
||
|
||
if prev_cb:
|
||
row.append(InlineKeyboardButton(text="⬅️", callback_data=prev_cb))
|
||
|
||
row.append(InlineKeyboardButton(text=str(page + 1), callback_data="..."))
|
||
|
||
if next_cb:
|
||
row.append(InlineKeyboardButton(text="➡️", callback_data=next_cb))
|
||
|
||
return row
|
||
|
||
|
||
def cart_btn(cart_items: int) -> InlineKeyboardButton:
|
||
return InlineKeyboardButton(text=f"🛒 Корзина ({cart_items})", callback_data="cart")
|
||
|
||
|
||
def get_back_to_catalogue(
|
||
parent_id: int,
|
||
*,
|
||
text: Optional[str] = "⬅️ Назад",
|
||
show_controls: bool = False,
|
||
cat_id: Optional[Union[str, int]] = None,
|
||
fallback_cb: Optional[str] = "menu:main",
|
||
) -> InlineKeyboardMarkup:
|
||
|
||
builder = InlineKeyboardBuilder()
|
||
|
||
if show_controls and cat_id:
|
||
builder.row(
|
||
InlineKeyboardButton(
|
||
text="➕ Категория", callback_data=f"new:category:{cat_id}"
|
||
),
|
||
InlineKeyboardButton(
|
||
text="➕ Товар", callback_data=f"new:product:{cat_id}"
|
||
),
|
||
)
|
||
|
||
builder.row(
|
||
InlineKeyboardButton(
|
||
text=text, callback_data=f"cat:{parent_id}" if parent_id else fallback_cb
|
||
),
|
||
)
|
||
|
||
return builder.as_markup()
|
||
|
||
|
||
def main_menu_kb(cart_items: int) -> InlineKeyboardMarkup:
|
||
return InlineKeyboardBuilder(
|
||
[
|
||
[
|
||
InlineKeyboardButton(text="📦 Каталог", callback_data="cat:root"),
|
||
InlineKeyboardButton(text="🔍", callback_data="find"),
|
||
],
|
||
[cart_btn(cart_items)],
|
||
]
|
||
).as_markup()
|
||
|
||
|
||
def render_category(
|
||
children: list[Category],
|
||
parent_id: Optional[Union[str, int]] = None,
|
||
show_menu: bool = False,
|
||
*,
|
||
cat_id: Optional[Union[str, int]] = None,
|
||
category_cb_factory: Callable[[Category], str],
|
||
is_admin: bool = False,
|
||
) -> InlineKeyboardMarkup:
|
||
btns = [
|
||
InlineKeyboardButton(
|
||
text=category.name,
|
||
callback_data=category_cb_factory(category),
|
||
)
|
||
for category in children
|
||
]
|
||
|
||
nav: list[list[InlineKeyboardButton]] = []
|
||
|
||
if is_admin:
|
||
cat_id = serialize_cat_id(cat_id)
|
||
nav.append(
|
||
[
|
||
InlineKeyboardButton(
|
||
text="➕ Категория",
|
||
callback_data=f"new:category:{cat_id}",
|
||
),
|
||
InlineKeyboardButton(
|
||
text="✍️", callback_data=f"edit:category:{cat_id}"
|
||
),
|
||
]
|
||
)
|
||
|
||
if show_menu:
|
||
nav.append([InlineKeyboardButton(text="⬅️ Назад", callback_data="menu:main")])
|
||
elif parent_id is not None:
|
||
nav.append(
|
||
[InlineKeyboardButton(text="⬅️ Назад", callback_data=f"cat:{parent_id}")]
|
||
)
|
||
|
||
return InlineKeyboardBuilder(
|
||
[btns[i : i + 2] for i in range(0, len(children), 2)] + nav,
|
||
).as_markup()
|
||
|
||
|
||
def render_products(
|
||
products: list[Product],
|
||
*,
|
||
product_cb_factory: Callable[[Product], str],
|
||
back_cb: Optional[str] = None,
|
||
prev_cb: Optional[str] = None,
|
||
next_cb: Optional[str] = None,
|
||
columns: int = 2,
|
||
is_admin: bool = False,
|
||
cat_id: Optional[int] = None,
|
||
) -> InlineKeyboardMarkup:
|
||
builder = InlineKeyboardBuilder()
|
||
|
||
if back_cb:
|
||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=back_cb))
|
||
|
||
for i in range(0, len(products), columns):
|
||
row = [
|
||
InlineKeyboardButton(
|
||
text=p.name,
|
||
callback_data=product_cb_factory(p),
|
||
)
|
||
for p in products[i : i + columns]
|
||
]
|
||
builder.row(*row)
|
||
|
||
if is_admin:
|
||
builder.row(
|
||
InlineKeyboardButton(text="➕ Товар", callback_data=f"new:product:{cat_id}")
|
||
)
|
||
|
||
nav_buttons = []
|
||
if prev_cb:
|
||
nav_buttons.append(InlineKeyboardButton(text="◀️", callback_data=prev_cb))
|
||
if next_cb:
|
||
nav_buttons.append(InlineKeyboardButton(text="▶️", callback_data=next_cb))
|
||
|
||
if nav_buttons:
|
||
builder.row(*nav_buttons)
|
||
|
||
return builder.as_markup()
|
||
|
||
|
||
def render_catalogue(view: CatalogueView, *, is_admin: bool):
|
||
if view.view_type == CatalogueType.CATEGORIES:
|
||
return render_category(
|
||
children=view.children,
|
||
parent_id=view.parent_id,
|
||
show_menu=view.show_menu,
|
||
category_cb_factory=lambda c: f"cat:{c.id}",
|
||
is_admin=is_admin,
|
||
cat_id=view.category,
|
||
)
|
||
if view.view_type == CatalogueType.PRODUCTS:
|
||
return render_products(
|
||
products=view.products[:PAGE_SIZE], # type: ignore
|
||
product_cb_factory=lambda p: f"product:{p.id}",
|
||
back_cb=f"cat:{view.parent_id}",
|
||
prev_cb=(
|
||
f"products:{view.category}:{view.page - 1}" if view.page > 0 else None
|
||
),
|
||
next_cb=(
|
||
f"products:{view.category}:{view.page + 1}" if view.has_next else None
|
||
),
|
||
is_admin=is_admin,
|
||
cat_id=view.category,
|
||
)
|
||
|
||
return get_back_to_catalogue(
|
||
view.parent_id, show_controls=is_admin, cat_id=serialize_cat_id(view.category)
|
||
)
|
||
|
||
|
||
def render_product_interactions(
|
||
*, product_id: int, category_id: int, show_cart: bool = False, cart_amount: int = 0
|
||
) -> InlineKeyboardMarkup:
|
||
btns: list[list[InlineKeyboardButton]] = []
|
||
|
||
if show_cart:
|
||
btns.append(
|
||
[
|
||
InlineKeyboardButton(
|
||
text="➖", callback_data=f"cart_action:remove:{product_id}"
|
||
),
|
||
InlineKeyboardButton(text=f"🛒 {cart_amount}", callback_data="..."),
|
||
InlineKeyboardButton(
|
||
text="➕", callback_data=f"cart_action:add:{product_id}"
|
||
),
|
||
]
|
||
)
|
||
btns.append(
|
||
[
|
||
InlineKeyboardButton(
|
||
text="❌ Удалить из корзины",
|
||
callback_data=f"cart_action:clear:{product_id}",
|
||
)
|
||
]
|
||
)
|
||
|
||
else:
|
||
btns.append(
|
||
[
|
||
InlineKeyboardButton(
|
||
text="🛒 В корзину", callback_data=f"cart_action:add:{product_id}"
|
||
)
|
||
]
|
||
)
|
||
|
||
btns.append([InlineKeyboardButton(text="⬅️", callback_data=f"cat:{category_id}")])
|
||
|
||
return InlineKeyboardBuilder(btns).as_markup()
|
||
|
||
|
||
def render_cart(
|
||
cart_items: list[CartItemDTO],
|
||
*,
|
||
page: int = 0,
|
||
show_prev: Optional[bool] = False,
|
||
show_next: Optional[bool] = False,
|
||
show_purchase: Optional[bool] = False,
|
||
) -> InlineKeyboardMarkup:
|
||
btns = [
|
||
InlineKeyboardButton(
|
||
text=f"🛒 {cart_item.quantity} | {cart_item.name} | {cart_item.total}₽",
|
||
callback_data=f"product:{cart_item.product_id}",
|
||
)
|
||
for cart_item in cart_items
|
||
]
|
||
|
||
nav = pagination_row(
|
||
page=page,
|
||
prev_cb=f"cart:{page - 1}" if show_prev else None,
|
||
next_cb=f"cart:{page + 1}" if show_next else None,
|
||
)
|
||
|
||
markup: list[list[InlineKeyboardButton]] = (
|
||
[
|
||
[
|
||
InlineKeyboardButton(
|
||
text="⬅️ Назад",
|
||
callback_data="menu:main",
|
||
)
|
||
],
|
||
]
|
||
+ [btns[i : i + 2] for i in range(0, len(cart_items), 2)]
|
||
+ [nav]
|
||
)
|
||
|
||
if show_purchase:
|
||
markup.append(
|
||
[InlineKeyboardButton(text="🔷 Оформить заказ", callback_data="checkout")]
|
||
)
|
||
|
||
return InlineKeyboardBuilder(markup).as_markup()
|
||
|
||
|
||
def order_confirmation(order_id: int) -> InlineKeyboardMarkup:
|
||
return InlineKeyboardBuilder(
|
||
[
|
||
[
|
||
InlineKeyboardButton(
|
||
text="🟢 Подтвердить заказ",
|
||
callback_data=f"confirm_order:{order_id}",
|
||
)
|
||
],
|
||
[InlineKeyboardButton(text="❌ Отменить", callback_data="menu:main")],
|
||
]
|
||
).as_markup()
|
||
|
||
|
||
def order_specs_confirmation(order_id: int) -> InlineKeyboardMarkup:
|
||
return InlineKeyboardBuilder(
|
||
[
|
||
[
|
||
InlineKeyboardButton(
|
||
text="🟢 Подтвердить",
|
||
callback_data=f"payment:{order_id}",
|
||
)
|
||
],
|
||
[InlineKeyboardButton(text="❌", callback_data="menu:main")],
|
||
]
|
||
).as_markup()
|
||
|
||
|
||
def confirm_payment(invoice_id: int):
|
||
return InlineKeyboardBuilder(
|
||
[
|
||
[
|
||
InlineKeyboardButton(
|
||
text="✅ Подтвердить оплату",
|
||
callback_data=f"refresh_payment:{invoice_id}",
|
||
)
|
||
]
|
||
]
|
||
).as_markup()
|