v0.30: product pages, categories, sale prices and order status timeline

This commit is contained in:
shop
2026-06-25 17:29:43 +03:00
parent 532351e1c9
commit e000264bb1
26 changed files with 1061 additions and 248 deletions
@@ -0,0 +1,60 @@
{{define "admin_categories.html"}}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Title}}</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/admin.css">
</head>
<body class="admin-body">
{{template "admin_nav" .}}
<main class="admin-main container">
<h1 class="admin-page-title">Категории</h1>
<section class="admin-section">
<h2 class="admin-section__title">Добавить категорию</h2>
<form method="POST" action="/admin/categories" class="admin-form admin-form--row">
<label class="admin-form__label">
Название
<input type="text" name="name" class="admin-form__input" required>
</label>
<label class="admin-form__label">
Описание
<input type="text" name="description" class="admin-form__input">
</label>
<button type="submit" class="btn btn--primary">Добавить</button>
</form>
</section>
<section class="admin-section">
<table class="admin-table">
<thead>
<tr>
<th>Название</th>
<th>Slug</th>
<th>Товаров</th>
<th></th>
</tr>
</thead>
<tbody>
{{range .Categories}}
<tr>
<td><strong>{{.Name}}</strong></td>
<td><a href="/category/{{.Slug}}" target="_blank">{{.Slug}}</a></td>
<td>{{.Count}}</td>
<td>
<form method="POST" action="/admin/categories/{{.ID}}/delete" onsubmit="return confirm('Удалить категорию?')">
<button type="submit" class="btn btn--danger btn--sm">Удалить</button>
</form>
</td>
</tr>
{{end}}
</tbody>
</table>
</section>
</main>
</body>
</html>
{{end}}
@@ -8,6 +8,7 @@
<nav class="admin-nav">
<a href="/admin/" class="admin-nav__link">Главная</a>
<a href="/admin/products" class="admin-nav__link">Товары</a>
<a href="/admin/categories" class="admin-nav__link">Категории</a>
<a href="/admin/products/new" class="admin-nav__link admin-nav__link--accent">+ Добавить</a>
</nav>
<div class="admin-header__user">
@@ -44,18 +44,21 @@
<input type="number" name="price" class="admin-form__input" value="{{printf "%.0f" .Product.Price}}" min="0" step="1" required>
</label>
<label class="admin-form__label">
Категория
<input type="text" name="category" class="admin-form__input" value="{{.Product.Category}}" list="categories">
<datalist id="categories">
<option value="Электроника">
<option value="Одежда">
<option value="Дом и сад">
<option value="Аксессуары">
<option value="Разное">
</datalist>
Цена со скидкой (₽)
<input type="number" name="sale_price" class="admin-form__input" value="{{if .Product.HasSale}}{{printf "%.0f" .Product.SalePrice}}{{end}}" min="0" step="1" placeholder="Без скидки">
</label>
</div>
<label class="admin-form__label">
Категория
<select name="category_id" class="admin-form__input">
<option value="">— без категории —</option>
{{range .Categories}}
<option value="{{.ID}}" {{if catSelected .ID $.Product.CategoryID}}selected{{end}}>{{.Name}}</option>
{{end}}
</select>
</label>
<label class="admin-form__checkbox">
<input type="checkbox" name="is_active" {{if .Product.IsActive}}checked{{end}}>
Показывать в каталоге
+99
View File
@@ -0,0 +1,99 @@
{{define "product.html"}}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Product.Name}} — Shop</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/shop.css">
</head>
<body>
{{template "shop_header" .}}
<main class="shop-page container">
<nav class="breadcrumb">
<a href="/">Главная</a>
{{if .Product.CategorySlug}}
<span>/</span>
<a href="/category/{{.Product.CategorySlug}}">{{.Product.Category}}</a>
{{end}}
<span>/</span>
<span>{{.Product.Name}}</span>
</nav>
<div class="product-detail">
<div class="product-detail__gallery">
<img src="{{.Product.ImageURL}}" alt="{{.Product.Name}}" class="product-detail__main">
{{if .Product.Images}}
<div class="product-detail__thumbs">
{{range .Product.Images}}
<img src="{{.FilePath}}" alt="">
{{end}}
</div>
{{end}}
</div>
<div class="product-detail__info">
{{if .Product.CategorySlug}}
<a href="/category/{{.Product.CategorySlug}}" class="product-detail__cat">{{.Product.Category}}</a>
{{end}}
<h1>{{.Product.Name}}</h1>
<div class="price-block price-block--lg">
{{if .Product.HasSale}}
<span class="price-block__old">{{printf "%.0f" .Product.Price}} ₽</span>
<span class="price-block__sale">{{printf "%.0f" .Product.SalePrice}} ₽</span>
<span class="price-block__badge">-{{.Product.DiscountPercent}}%</span>
{{else}}
<span class="price-block__current">{{printf "%.0f" .Product.Price}} ₽</span>
{{end}}
</div>
<p class="product-detail__short">{{plain .Product.Description}}</p>
<form method="POST" action="/cart/add" class="product-detail__buy">
<input type="hidden" name="product_id" value="{{.Product.ID}}">
<input type="number" name="quantity" value="1" min="1" max="99" class="qty-input">
<button type="submit" class="btn btn--primary btn--lg">Добавить в корзину</button>
</form>
</div>
</div>
{{if .Product.Details}}
<section class="product-detail__content shop-card">
<h2>Описание</h2>
<div class="product-detail__html">{{safeHTML .Product.Details}}</div>
</section>
{{end}}
</main>
</body>
</html>
{{end}}
{{define "category.html"}}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Category.Name}} — Shop</title>
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/shop.css">
</head>
<body>
{{template "shop_header" .}}
<main class="shop-page container">
<nav class="breadcrumb">
<a href="/">Главная</a>
<span>/</span>
<span>{{.Category.Name}}</span>
</nav>
<h1>{{.Category.Name}}</h1>
{{if .Category.Description}}<p class="shop-hint">{{.Category.Description}}</p>{{end}}
<div class="products">
{{range .Products}}
{{template "product_card" .}}
{{else}}
<p class="empty">В этой категории пока нет товаров.</p>
{{end}}
</div>
</main>
</body>
</html>
{{end}}
+2 -19
View File
@@ -43,7 +43,7 @@
<h2 class="section__title">Популярные категории</h2>
<div class="categories">
{{range .Categories}}
<a href="#catalog" class="category-card">
<a href="/category/{{.Slug}}" class="category-card">
<span class="category-card__name">{{.Name}}</span>
<span class="category-card__count">{{.Count}} товаров</span>
</a>
@@ -60,24 +60,7 @@
</div>
<div class="products">
{{range .Products}}
<article class="product-card">
<div class="product-card__image">
<img src="{{.ImageURL}}" alt="{{.Name}}" loading="lazy">
<span class="product-card__badge">{{.Category}}</span>
</div>
<div class="product-card__body">
<h3 class="product-card__title">{{.Name}}</h3>
<p class="product-card__desc">{{plain .Description}}</p>
<div class="product-card__footer">
<span class="product-card__price">{{printf "%.0f" .Price}} ₽</span>
<form method="POST" action="/cart/add">
<input type="hidden" name="product_id" value="{{.ID}}">
<input type="hidden" name="quantity" value="1">
<button type="submit" class="btn btn--primary btn--sm">В корзину</button>
</form>
</div>
</div>
</article>
{{template "product_card" .}}
{{else}}
<p class="empty">Товары скоро появятся в каталоге.</p>
{{end}}
@@ -0,0 +1,30 @@
{{define "product_card"}}
<article class="product-card">
<a href="/product/{{.ID}}" class="product-card__link">
<div class="product-card__image">
<img src="{{.ImageURL}}" alt="{{.Name}}" loading="lazy">
{{if .HasSale}}<span class="product-card__sale">-{{.DiscountPercent}}%</span>{{end}}
<span class="product-card__badge">{{.Category}}</span>
</div>
<div class="product-card__body">
<h3 class="product-card__title">{{.Name}}</h3>
<p class="product-card__desc">{{plain .Description}}</p>
<div class="product-card__footer">
<div class="price-block">
{{if .HasSale}}
<span class="price-block__old">{{printf "%.0f" .Price}} ₽</span>
<span class="product-card__price price-block__sale">{{printf "%.0f" .SalePrice}} ₽</span>
{{else}}
<span class="product-card__price">{{printf "%.0f" .Price}} ₽</span>
{{end}}
</div>
</div>
</div>
</a>
<form method="POST" action="/cart/add" class="product-card__cart">
<input type="hidden" name="product_id" value="{{.ID}}">
<input type="hidden" name="quantity" value="1">
<button type="submit" class="btn btn--primary btn--sm">В корзину</button>
</form>
</article>
{{end}}
+1 -1
View File
@@ -6,9 +6,9 @@
<span class="logo__text">Shop</span>
</a>
<nav class="nav">
<a href="/#catalog" class="nav__link">Каталог</a>
<a href="/#catalog" class="nav__link">Каталог</a>
<a href="/#categories" class="nav__link">Категории</a>
<a href="/#delivery" class="nav__link">Доставка</a>
</nav>
<div class="header__actions">
{{if .User}}
+26 -5
View File
@@ -19,8 +19,15 @@
<div class="cart-item">
<img src="{{.Product.ImageURL}}" alt="{{.Product.Name}}" class="cart-item__img">
<div class="cart-item__info">
<h3>{{.Product.Name}}</h3>
<p class="cart-item__price">{{printf "%.0f" .Product.Price}} ₽</p>
<h3><a href="/product/{{.ProductID}}">{{.Product.Name}}</a></h3>
<div class="price-block">
{{if .Product.HasSale}}
<span class="price-block__old">{{printf "%.0f" .Product.Price}} ₽</span>
<span class="price-block__sale">{{printf "%.0f" .Product.SalePrice}} ₽</span>
{{else}}
<span class="cart-item__price">{{printf "%.0f" .Product.Price}} ₽</span>
{{end}}
</div>
</div>
<form method="POST" action="/cart/update" class="cart-item__qty">
<input type="hidden" name="product_id" value="{{.ProductID}}">
@@ -164,10 +171,24 @@
{{range .Orders}}
<div class="order-card">
<div class="order-card__head">
<strong>Заказ #{{.ID}}</strong>
<span>{{.CreatedAt.Format "02.01.2006 15:04"}}</span>
<span class="admin-badge admin-badge--ok">{{.Status}}</span>
<div>
<strong>Заказ #{{.ID}}</strong>
<span class="order-card__date">{{.CreatedAt.Format "02.01.2006 15:04"}}</span>
</div>
<span class="order-status {{orderClass .Status}}">{{orderLabel .Status}}</span>
</div>
{{if ne .Status "cancelled"}}
<div class="order-timeline">
{{range orderTimeline}}
<div class="order-timeline__step {{if stepCurrent $.Status .}}order-timeline__step--current{{end}} {{if stepDone $.Status .}}order-timeline__step--done{{end}}">
<span class="order-timeline__dot"></span>
<span class="order-timeline__label">{{orderLabel .}}</span>
</div>
{{end}}
</div>
{{end}}
<ul class="order-card__items">
{{range .Items}}
<li>{{.ProductName}} × {{.Quantity}} — {{printf "%.0f" .Price}} ₽</li>