diff options
| author | Mole Shang <135e2@135e2.dev> | 2026-01-24 19:47:41 +0800 |
|---|---|---|
| committer | Mole Shang <135e2@135e2.dev> | 2026-01-24 19:47:41 +0800 |
| commit | b45660fbcf5dd22188354bfa0193845e568bda53 (patch) | |
| tree | 172825ef6e210ce03fc2241395c6cbc389538a2b /store/templates | |
| download | seu-bookstore-b45660fbcf5dd22188354bfa0193845e568bda53.tar.gz seu-bookstore-b45660fbcf5dd22188354bfa0193845e568bda53.tar.bz2 seu-bookstore-b45660fbcf5dd22188354bfa0193845e568bda53.zip | |
initial commit
Diffstat (limited to 'store/templates')
| -rw-r--r-- | store/templates/store/.book_list.html.kate-swp | 0 | ||||
| -rw-r--r-- | store/templates/store/add_edit_book.html | 84 | ||||
| -rw-r--r-- | store/templates/store/base.html | 83 | ||||
| -rw-r--r-- | store/templates/store/book_detail.html | 69 | ||||
| -rw-r--r-- | store/templates/store/book_list.html | 221 | ||||
| -rw-r--r-- | store/templates/store/cart.html | 52 | ||||
| -rw-r--r-- | store/templates/store/checkout.html | 47 | ||||
| -rw-r--r-- | store/templates/store/delete_book_confirm.html | 16 | ||||
| -rw-r--r-- | store/templates/store/import_books.html | 46 | ||||
| -rw-r--r-- | store/templates/store/login.html | 32 | ||||
| -rw-r--r-- | store/templates/store/order_list.html | 56 | ||||
| -rw-r--r-- | store/templates/store/order_management.html | 111 | ||||
| -rw-r--r-- | store/templates/store/profile.html | 39 | ||||
| -rw-r--r-- | store/templates/store/register.html | 49 |
14 files changed, 905 insertions, 0 deletions
diff --git a/store/templates/store/.book_list.html.kate-swp b/store/templates/store/.book_list.html.kate-swp new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/store/templates/store/.book_list.html.kate-swp diff --git a/store/templates/store/add_edit_book.html b/store/templates/store/add_edit_book.html new file mode 100644 index 0000000..c87a9c7 --- /dev/null +++ b/store/templates/store/add_edit_book.html @@ -0,0 +1,84 @@ +{% extends 'store/base.html' %} +{% load static %} + +{% block content %} +<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;"> + <mdui-card style="width: 100%; max-width: 600px; padding: 2rem;"> + <h2 style="margin-top: 0; text-align: center;">{{ title }}</h2> + <form method="POST" enctype="multipart/form-data"> + {% csrf_token %} + {% if form.non_field_errors %} + <div style="color: var(--mdui-color-error); margin-bottom: 1rem;"> + {% for error in form.non_field_errors %} + <p>{{ error }}</p> + {% endfor %} + </div> + {% endif %} + + {% for field in form %} + {% if field.name != 'cover_url' and field.name != 'cover_upload' %} + <div style="margin-bottom: 1rem;"> + <mdui-text-field + label="{{ field.label }}" + name="{{ field.name }}" + value="{{ field.value|default:'' }}" + {% if field.field.required %}required{% endif %} + {% if field.name == 'description' %}rows="5"{% endif %} + {% if field.field.widget.input_type == 'number' %}type="number"{% endif %} + style="width: 100%;" + ></mdui-text-field> + </div> + {% endif %} + {% endfor %} + + <mdui-select label="Cover Image Type" value="url" style="margin-top: 0.5rem; margin-bottom: 1rem; width: 100%;" onchange="toggleCoverInput(this.value);"> + <mdui-menu-item value="url">URL</mdui-menu-item> + <mdui-menu-item value="file">File Upload</mdui-menu-item> + </mdui-select> + + <div id="cover-url-input" style="margin-bottom: 1rem;"> + <mdui-text-field label="{{ form.cover_url.label }}" name="{{ form.cover_url.name }}" value="{{ form.cover_url.value|default:'' }}" style="width: 100%;"></mdui-text-field> + </div> + <div id="cover-file-input"> + <input type="file" name="{{ form.cover_upload.name }}" style="display: none"> + </div> + + {% if form.cover_url.errors or form.cover_upload.errors or form.non_field_errors %} + <div style="color: var(--mdui-color-error); font-size: 0.875rem; margin-top: 0.25rem;"> + {{ form.cover_url.errors }} + {{ form.cover_upload.errors }} + {{ form.non_field_errors }} + </div> + {% endif %} + + <div style="display: flex; gap: 1rem; margin-top: 2rem; justify-content: flex-end;"> + <mdui-button href="{% if book %}{% url 'book_detail' book.id %}{% else %}{% url 'book_list' %}{% endif %}" variant="text" icon="cancel">Cancel</mdui-button> + <mdui-button type="submit" icon="save">Save</mdui-button> + </div> + </form> +{% endblock %} + +{% block extra_script %} + <script> + function toggleCoverInput(type) { + const urlInput = document.getElementById('cover-url-input'); + const fileInput = document.getElementById('cover-file-input'); + const urlField = urlInput.querySelector('mdui-text-field'); + const fileField = fileInput.querySelector('input'); + + if (type === 'url') { + urlInput.style.display = 'block'; + urlField.disabled = false; + } else { + urlInput.style.display = 'none'; + urlField.disabled = true; + fileField.showPicker(); + } + } + // Initialize on page load + document.addEventListener('DOMContentLoaded', () => { + const select = document.querySelector('mdui-select'); + toggleCoverInput(select.value); + }); + </script> +{% endblock %} diff --git a/store/templates/store/base.html b/store/templates/store/base.html new file mode 100644 index 0000000..18a9557 --- /dev/null +++ b/store/templates/store/base.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<html lang="en" class="mdui-theme-auto"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>SEU Bookstore</title> + <link rel="stylesheet" href="https://unpkg.com/mdui@2/mdui.css"> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons+Outlined" rel="stylesheet"> + <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet"> + <style> + html, body { height: 100%; margin: 0; padding: 0; } + body { font-family: 'Roboto', sans-serif; } + .main-container { max-width: 1200px; margin: 0 auto; padding: 2rem 1rem; } + </style> + <script src="https://unpkg.com/mdui@2/mdui.global.js"></script> +</head> +<body> + <mdui-layout full-height> + <mdui-top-app-bar variant="center-aligned" style="margin: 0 2rem;"> + <mdui-top-app-bar-title href="/">SEU Bookstore</mdui-top-app-bar-title> + + <mdui-dropdown> + <mdui-avatar slot="trigger" {% if user.is_authenticated and user.avatar %}src="{{ user.avatar.url }}"{% else %}icon="people_alt"{% endif %} style="cursor: pointer;">{% if user.is_authenticated and not user.avatar %}{{ user.username|first|upper }}{% endif %}</mdui-avatar> + <mdui-menu> + {% if user.is_authenticated %} + <mdui-menu-item href="{% url 'profile' %}" icon="person">Profile</mdui-menu-item> + <mdui-menu-item href="{% url 'logout' %}" icon="logout">Logout</mdui-menu-item> + {% else %} + <mdui-menu-item href="{% url 'login' %}" icon="login">Login</mdui-menu-item> + <mdui-menu-item href="{% url 'register' %}" icon="person_add">Register</mdui-menu-item> + {% endif %} + </mdui-menu> + </mdui-dropdown> + </mdui-top-app-bar> + + <mdui-navigation-rail divider> + <mdui-navigation-rail-item icon="library_books" href="{% url 'book_list' %}">Books</mdui-navigation-rail-item> + <mdui-navigation-rail-item icon="shopping_cart" href="{% url 'cart_detail' %}">Cart</mdui-navigation-rail-item> + <mdui-navigation-rail-item icon="receipt_long" href="{% url 'order_list' %}">Orders</mdui-navigation-rail-item> + + <div style="flex-grow: 1"></div> + + {% if user.is_staff %} + <mdui-navigation-rail-item slot="bottom" href="{% url 'order_management' %}" icon="local_shipping">Manage</mdui-navigation-rail-item> + {% endif %} + </mdui-navigation-rail> + + + <mdui-layout-main> + <div class="main-container"> + {% block content %} + {% endblock %} + </div> + </mdui-layout-main> + </mdui-layout> + + <script> + window.mdui.setColorScheme("#9ecaff"); + document.addEventListener('DOMContentLoaded', function () { + const currentPath = window.location.pathname; + const railItems = document.querySelectorAll('mdui-navigation-rail-item'); + + railItems.forEach(item => { + if (item.getAttribute('href') === currentPath) { + item.active = true; + } + }); + + {% if messages %} + {% for message in messages %} + const snackbar = mdui.snackbar({ + message: '{{ message }}', + closeable: true + }); + {% endfor %} + {% endif %} + }); + </script> + {% block extra_script %}{% endblock %} +</body> +</html> + diff --git a/store/templates/store/book_detail.html b/store/templates/store/book_detail.html new file mode 100644 index 0000000..e9cc2b8 --- /dev/null +++ b/store/templates/store/book_detail.html @@ -0,0 +1,69 @@ +{% extends 'store/base.html' %} + +{% block content %} + <div style="display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 2rem;"> + <div style="flex: 1; min-width: 300px; max-width: 400px;"> + {% if book.cover %} + <img src="{{ book.cover }}" alt="{{ book.title }}" style="width: 100%; border-radius: 12px; box-shadow: var(--mdui-elevation-level2);"> + {% else %} + <div style="width: 100%; height: 400px; background-color: var(--mdui-color-surface-container-highest); display: flex; align-items: center; justify-content: center; border-radius: 12px; color: var(--mdui-color-on-surface-variant);"> + No Image Available + </div> + {% endif %} + </div> + <div style="flex: 2; min-width: 300px;"> + <div class="mdui-prose"> + <h1>{{ book.title }}</h1> + <h3>{{ book.author }}</h3> + <p><strong>Publisher:</strong> {{ book.publisher }}</p> + <p><strong>ISBN:</strong> {{ book.isbn }}</p> + <p><strong>Price:</strong> {{ book.price }}</p> + <p><strong>Stock:</strong> {{ book.stock }}</p> + <p>{{ book.description|safe }}</p> + </div> + + <div style="margin-top: 1.5rem; display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;"> + <form action="{% url 'add_to_cart' book.id %}" method="POST" style="display: flex; align-items: center; gap: 1rem;"> + {% csrf_token %} + <mdui-text-field type="number" name="quantity" value="1" min="1" max="{{ book.stock }}" style="width: 80px;"></mdui-text-field> + <mdui-button type="submit" variant="filled" icon="shopping_cart">Add to Cart</mdui-button> + </form> + <mdui-button variant="outlined" href="{% url 'book_list' %}" icon="arrow_back">Back to List</mdui-button> + + {% if user.is_staff %} + <mdui-button variant="tonal" href="{% url 'edit_book' book.id %}" icon="edit">Edit</mdui-button> + <mdui-button variant="text" style="color: var(--mdui-color-error);" href="{% url 'delete_book' book.id %}" icon="delete">Delete</mdui-button> + {% endif %} + </div> + </div> + </div> + + <mdui-divider></mdui-divider> + + <div style="margin-top: 2rem;"> + <h2>Comments</h2> + {% if user.is_authenticated %} + <form action="{% url 'post_comment' book.id %}" method="POST" style="margin-bottom: 2rem; max-width: 600px;"> + {% csrf_token %} + <mdui-text-field name="content" label="Add a comment" rows="3" required style="width: 100%; margin-bottom: 1rem;"></mdui-text-field> + <mdui-button type="submit" icon="send">Post Comment</mdui-button> + </form> + {% else %} + <p><a href="{% url 'login' %}">Login</a> to post a comment.</p> + {% endif %} + + <div style="display: flex; flex-direction: column; gap: 1rem;"> + {% for comment in comments %} + <mdui-card variant="outlined" style="padding: 1rem;"> + <div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem;"> + <span style="font-weight: bold;">{{ comment.user.username }}</span> + <span style="color: var(--mdui-color-on-surface-variant); font-size: 0.9em;">{{ comment.createdAt|date:"M d, Y H:i" }}</span> + </div> + <div>{{ comment.content }}</div> + </mdui-card> + {% empty %} + <p>No comments yet.</p> + {% endfor %} + </div> + </div> +{% endblock %} diff --git a/store/templates/store/book_list.html b/store/templates/store/book_list.html new file mode 100644 index 0000000..5d68340 --- /dev/null +++ b/store/templates/store/book_list.html @@ -0,0 +1,221 @@ +{% extends 'store/base.html' %} +{% load mathfilters %} +{% block content %} +<div style="display: flex; flex-direction: column; gap: 1rem; margin-bottom: 2rem"> + <div style="display: flex; justify-content: space-between; align-items: center;"> + <h1>Books</h1> + <div style="display: flex; gap: 0.5rem; align-items: center"> + <mdui-button icon="search" id="search-toggle">Search</mdui-button> + {% if user.is_staff %} + <mdui-button href="{% url 'import_books' %}" icon="upload">Import Books</mdui-button> + <mdui-button href="{% url 'add_book' %}" icon="add">Add Book</mdui-button> + {% endif %} + </div> + </div> + + <form id="search-form" method="GET" style="display: {% if query %}flex{% else %}none{% endif %}; gap: 1rem; align-items: flex-start;"> + <mdui-text-field + name="q" + value="{{ query|default:'' }}" + placeholder="Search by title or author" + icon="search" + style="flex-grow: 1;" + clearable + ></mdui-text-field> + <mdui-button type="submit" variant="filled" icon="search">Search</mdui-button> + {% if query %} + <mdui-button href="{% url 'book_list' %}" variant="text" icon="clear">Clear</mdui-button> + {% endif %} + </form> +</div> + +<div style=" + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1.5rem; + "> + {% for book in books %} + <mdui-card clickable id="{% url 'book_detail' book.id %}"> + <div style="display: flex; flex-direction: column"> + {% if book.cover %} + <img src="{{ book.cover }}" alt="{{ book.title }}" + style="width: 100%; height: 250px; object-fit: cover; flex-shrink: 0" /> + {% else %} + <div style=" + height: 250px; + background-color: var(--mdui-color-surface-container-highest); + display: flex; + align-items: center; + justify-content: center; + color: var(--mdui-color-on-surface-variant); + flex-shrink: 0; + "> + No Image + </div> + {% endif %} + + <div style=" + padding: 1rem; + flex-grow: 1; + display: flex; + flex-direction: column; + "> + <div style="margin-bottom: 1rem"> + <h3 style=" + margin: 0 0 0.5rem 0; + font-size: 1.25rem; + line-height: 1.4; + color: var(--mdui-color-on-surface); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + "> + {{ book.title }} + </h3> + <p style=" + margin: 0; + color: var(--mdui-color-on-surface-variant); + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + "> + {{ book.author }} + </p> + </div> + + <div style=" + margin-top: auto; + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--mdui-color-surface-container-highest); + "> + <span style="font-weight: bold; font-size: 1.1rem; color: var(--mdui-color-primary);">{{ book.price }}¥</span> + <div> + <form action="{% url 'add_to_cart' book.id %}" method="POST" style="display: inline"> + {% csrf_token %} + <mdui-button type="submit" variant="tonal">Add to Cart</mdui-button> + </form> + </div> + </div> + </div> + </div> + </mdui-card> + {% empty %} + <div style=" + grid-column: 1 / -1; + text-align: center; + padding: 3rem; + color: var(--mdui-color-on-surface-variant); + "> + <mdui-icon name="library_books--outlined" + style="font-size: 4rem; display: block; margin: 0 auto 1rem"></mdui-icon> + <div class="mdui-prose"> + <h2>No books yet</h2> + {% if query %} + <p>We couldn't find any books matching "{{ query }}".</p> + <mdui-button href="{% url 'book_list' %}" variant="text">Clear Search</mdui-button> + {% else %} + <p>Check back later!</p> + {% endif %} + </div> + </div> + {% endfor %} +</div> + +{% if books.paginator.num_pages > 1 %} +<div style=" + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + margin-top: 3rem; + width: 100%; + "> + <div style=" + display: grid; + grid-template-columns: 1fr auto 1fr; + gap: 1rem; + align-items: center; + width: 100%; + max-width: 600px; + "> + <div style="justify-self: end"> + <mdui-button {% if books.has_previous %}onclick="jumpToPage({{ books.previous_page_number }})" {% else + %}disabled{% endif %} variant="outlined" icon="arrow_back"> + {% if books.has_previous %}{{ books.number|sub:1 }}{% endif %} + </mdui-button> + </div> + + <div style=" + display: flex; + align-items: center; + gap: 0.5rem; + justify-content: center; + "> + <mdui-text-field variant="outlined" id="page-jump-input" value="{{ books.number }}" + style="width: 60px"></mdui-text-field> + <span style="color: var(--mdui-color-on-surface); white-space: nowrap"> + of {{ books.paginator.num_pages }} + </span> + </div> + + <div style="justify-self: start"> + <mdui-button {% if books.has_next %}onclick="jumpToPage({{ books.next_page_number }})" {% else %}disabled{% endif %} variant="outlined" end-icon="arrow_forward"> + {% if books.has_next %}{{ books.number|add:1 }}{% endif %} + </mdui-button> + </div> + </div> +</div> +{% endif %} +{% endblock %} + +{% block extra_script %} +<script> + const jumpToPage = (i) => { + const page = parseInt(i); + if (!isNaN(page)) { + const params = new URLSearchParams(window.location.search); + params.set("page", page); + window.location.search = params.toString(); + } + }; + + document.addEventListener("DOMContentLoaded", function () { + const toggleBtn = document.getElementById("search-toggle"); + const searchForm = document.getElementById("search-form"); + + toggleBtn.addEventListener('click', () => { + if (searchForm.style.display === 'none') { + searchForm.style.display = 'flex'; + } else { + searchForm.style.display = 'none'; + } + }); + + const pageInput = document.getElementById("page-jump-input"); + if (pageInput) { + + pageInput.addEventListener("keydown", (e) => { + if (e.key === "Enter") jumpToPage(pageInput.value); + }); + pageInput.addEventListener("change", () => jumpToPage(pageInput.value)); + } + + const bookCards = document.querySelectorAll("mdui-card"); + bookCards.forEach((card) => { + const form = card.querySelector("form"); + if (form) { + card.addEventListener("click", function (event) { + if (!form.contains(event.target)) { + window.location.href = this.id; + } + }); + } + }); + }); +</script> +{% endblock %} diff --git a/store/templates/store/cart.html b/store/templates/store/cart.html new file mode 100644 index 0000000..f634c58 --- /dev/null +++ b/store/templates/store/cart.html @@ -0,0 +1,52 @@ +{% extends 'store/base.html' %} + +{% block content %} + <h2>Shopping Cart</h2> + {% if cart_items %} + <div style="overflow-x: auto;"> + <table style="width: 100%; border-collapse: collapse; margin-bottom: 2rem;"> + <thead> + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); text-align: left;"> + <th style="padding: 1rem;">Book</th> + <th style="padding: 1rem;">Price</th> + <th style="padding: 1rem;">Quantity</th> + <th style="padding: 1rem;">Total</th> + <th style="padding: 1rem;">Actions</th> + </tr> + </thead> + <tbody> + {% for item in cart_items %} + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant);"> + <td style="padding: 1rem;">{{ item.book.title }}</td> + <td style="padding: 1rem;">{{ item.book.price }}¥</td> + <td style="padding: 1rem;"> + <form action="{% url 'update_cart_quantity' item.book.id %}" method="POST" style="display: flex; align-items: center; gap: 0.5rem;"> + {% csrf_token %} + <input type="number" name="quantity" value="{{ item.quantity }}" min="1" style="width: 60px; padding: 0.5rem; border: 1px solid var(--mdui-color-outline); border-radius: 4px;"> + <mdui-button type="submit" variant="text" icon="refresh">Update</mdui-button> + </form> + </td> + <td style="padding: 1rem;">{{ item.total_price }}¥</td> + <td style="padding: 1rem;"> + <mdui-button href="{% url 'remove_from_cart' item.book.id %}" variant="text" style="color: var(--mdui-color-error);" icon="delete">Remove</mdui-button> + </td> + </tr> + {% endfor %} + </tbody> + <tfoot> + <tr> + <td colspan="3" style="text-align: right; padding: 1rem;"><strong>Total:</strong></td> + <td style="padding: 1rem;"><strong>{{ total_price }}¥</strong></td> + <td></td> + </tr> + </tfoot> + </table> + </div> + <div style="display: flex; justify-content: flex-end;"> + <mdui-button href="{% url 'checkout' %}" variant="filled" icon="payment">Checkout</mdui-button> + </div> + {% else %} + <p>Your cart is empty.</p> + <mdui-button href="{% url 'book_list' %}" variant="filled" icon="shopping_basket">Go Shopping</mdui-button> + {% endif %} +{% endblock %} diff --git a/store/templates/store/checkout.html b/store/templates/store/checkout.html new file mode 100644 index 0000000..11dc99c --- /dev/null +++ b/store/templates/store/checkout.html @@ -0,0 +1,47 @@ +{% extends 'store/base.html' %} + +{% block content %} + <h2>Checkout</h2> + <div style="display: flex; gap: 2rem; flex-wrap: wrap;"> + <div style="flex: 2; min-width: 300px;"> + <h4>Order Summary</h4> + <table style="width: 100%; border-collapse: collapse;"> + <thead> + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); text-align: left;"> + <th style="padding: 0.5rem;">Book</th> + <th style="padding: 0.5rem;">Price</th> + <th style="padding: 0.5rem;">Quantity</th> + <th style="padding: 0.5rem;">Total</th> + </tr> + </thead> + <tbody> + {% for item in cart_items %} + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant);"> + <td style="padding: 0.5rem;">{{ item.book.title }}</td> + <td style="padding: 0.5rem;">${{ item.book.price }}</td> + <td style="padding: 0.5rem;">{{ item.quantity }}</td> + <td style="padding: 0.5rem;">${{ item.total_price }}</td> + </tr> + {% endfor %} + </tbody> + <tfoot> + <tr> + <td colspan="3" style="text-align: right; padding: 0.5rem;"><strong>Total:</strong></td> + <td style="padding: 0.5rem;"><strong>${{ total_price }}</strong></td> + </tr> + </tfoot> + </table> + </div> + <div style="flex: 1; min-width: 300px;"> + <h4>Shipping Information</h4> + <form method="POST"> + {% csrf_token %} + <div style="margin-bottom: 1rem;"> + <mdui-text-field label="Address" name="address" rows="3" required value="{{ user.address|default:'' }}" style="width: 100%;"></mdui-text-field> + </div> + <mdui-button type="submit" full-width icon="check">Place Order</mdui-button> + </form> + </div> + </div> +{% endblock %} + diff --git a/store/templates/store/delete_book_confirm.html b/store/templates/store/delete_book_confirm.html new file mode 100644 index 0000000..8450973 --- /dev/null +++ b/store/templates/store/delete_book_confirm.html @@ -0,0 +1,16 @@ +{% extends 'store/base.html' %} +{% load static %} + +{% block content %} +<div style="display: flex; justify-content: center; margin-top: 3rem;"> + <mdui-card style="width: 100%; max-width: 400px; padding: 2rem;"> + <h2 style="margin-top: 0; text-align: center;">Confirm Deletion</h2> + <p style="text-align: center;">Are you sure you want to delete "<strong>{{ book.title }}</strong>"?</p> + <form method="POST" style="display: flex; justify-content: center; gap: 1rem; margin-top: 2rem;"> + {% csrf_token %} + <mdui-button href="{% url 'book_detail' book.id %}" variant="text" icon="cancel">Cancel</mdui-button> + <mdui-button type="submit" style="background-color: var(--mdui-color-error); color: var(--mdui-color-on-error);" icon="delete">Delete</mdui-button> + </form> + </mdui-card> +</div> +{% endblock %} diff --git a/store/templates/store/import_books.html b/store/templates/store/import_books.html new file mode 100644 index 0000000..81d4f0e --- /dev/null +++ b/store/templates/store/import_books.html @@ -0,0 +1,46 @@ +{% extends 'store/base.html' %} +{% load static %} + +{% block content %} +<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;"> + <mdui-card style="width: 100%; max-width: 600px; padding: 2rem;"> + <h2 style="margin-top: 0; text-align: center;">{{ title }}</h2> + <div class="mdui-prose" style="margin-bottom: 1.5rem;"> + <p>Upload a JSONL file to import books. Each line should be a JSON object with a "metadata" key containing book details.</p> + </div> + <form method="POST" enctype="multipart/form-data"> + {% csrf_token %} + {% if form.non_field_errors %} + <div style="color: var(--mdui-color-error); margin-bottom: 1rem;"> + {% for error in form.non_field_errors %} + <p>{{ error }}</p> + {% endfor %} + </div> + {% endif %} + + <div style="margin-bottom: 1.5rem;"> + <div style="display: flex; flex-direction: column; gap: 0.5rem; align-items: center; padding: 2rem; border: 2px dashed var(--mdui-color-outline-variant); border-radius: 12px; background-color: var(--mdui-color-surface-container-low);"> + <mdui-icon name="upload_file" style="font-size: 3rem; color: var(--mdui-color-primary);"></mdui-icon> + <input type="file" name="file" id="jsonl-file-input" accept=".jsonl,.json" required style="display: none;"> + <mdui-button type="button" variant="tonal" onclick="document.getElementById('jsonl-file-input').click()">Select JSONL File</mdui-button> + <div id="file-name-display" style="margin-top: 0.5rem; color: var(--mdui-color-on-surface-variant); font-size: 0.9rem;">No file selected</div> + </div> + </div> + + <div style="display: flex; gap: 1rem; margin-top: 2rem; justify-content: flex-end;"> + <mdui-button href="{% url 'book_list' %}" variant="text">Cancel</mdui-button> + <mdui-button type="submit" icon="upload">Import</mdui-button> + </div> + </form> + </mdui-card> +</div> +{% endblock %} + +{% block extra_script %} + <script> + document.getElementById('jsonl-file-input').addEventListener('change', function(e) { + const fileName = e.target.files[0] ? e.target.files[0].name : 'No file selected'; + document.getElementById('file-name-display').textContent = fileName; + }); + </script> +{% endblock %} diff --git a/store/templates/store/login.html b/store/templates/store/login.html new file mode 100644 index 0000000..ff34b5e --- /dev/null +++ b/store/templates/store/login.html @@ -0,0 +1,32 @@ +{% extends 'store/base.html' %} +{% load static %} + +{% block content %} +<div style="display: flex; justify-content: center; margin-top: 3rem;"> + <mdui-card style="width: 100%; max-width: 400px; padding: 2rem;"> + <h2 style="margin-top: 0; text-align: center;">Login</h2> + <form method="POST"> + {% csrf_token %} + {% if form.non_field_errors %} + <div style="color: var(--mdui-color-error); margin-bottom: 1rem;"> + {% for error in form.non_field_errors %} + <p>{{ error }}</p> + {% endfor %} + </div> + {% endif %} + + <div style="margin-bottom: 1rem;"> + <mdui-text-field label="Username" name="username" required style="width: 100%;"></mdui-text-field> + </div> + <div style="margin-bottom: 1.5rem;"> + <mdui-text-field label="Password" name="password" type="password" required style="width: 100%;"></mdui-text-field> + </div> + + <mdui-button type="submit" full-width icon="login">Login</mdui-button> + </form> + <div style="margin-top: 1rem; text-align: center;"> + <p>Don't have an account? <a href="{% url 'register' %}" style="color: var(--mdui-color-primary);">Register</a></p> + </div> + </mdui-card> +</div> +{% endblock %} diff --git a/store/templates/store/order_list.html b/store/templates/store/order_list.html new file mode 100644 index 0000000..c45680e --- /dev/null +++ b/store/templates/store/order_list.html @@ -0,0 +1,56 @@ +{% extends 'store/base.html' %} + +{% block content %} + <h2>My Orders</h2> + {% if orders %} + <mdui-collapse accordion> + {% for order in orders %} + <mdui-collapse-item> + <mdui-list-item slot="header" + {% if order.status == 1 %} + icon="pending_actions" + {% elif order.status == 2 %} + icon="done_all" + {% elif order.status == 3 %} + icon="error_outline" + {% endif %}> + Order #{{ order.id }} - {{ order.createdAt|date:"M d, Y" }} - {{ order.total_price }}¥ - + {% if order.status == 1 %} + <span style="color: var(--mdui-color-secondary);">Pending</span> + {% elif order.status == 2 %} + <span style="color: var(--mdui-color-success);">Shipped</span> + {% elif order.status == 3 %} + <span style="color: var(--mdui-color-error);">Rejected</span> + {% endif %} + </mdui-list-item> + <div style="padding: 1rem;"> + <p><strong>Shipping Address:</strong> {{ order.address }}</p> + <table style="width: 100%; border-collapse: collapse;"> + <thead> + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); text-align: left;"> + <th style="padding: 0.5rem;">Book</th> + <th style="padding: 0.5rem;">Price</th> + <th style="padding: 0.5rem;">Quantity</th> + <th style="padding: 0.5rem;">Subtotal</th> + </tr> + </thead> + <tbody> + {% for item in order.items.all %} + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant);"> + <td style="padding: 0.5rem;">{{ item.book.title }}</td> + <td style="padding: 0.5rem;">{{ item.bookPrice }}¥</td> + <td style="padding: 0.5rem;">{{ item.amount }}</td> + <td style="padding: 0.5rem;">{{ item.total_price }}¥</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </mdui-collapse-item> + {% endfor %} + </mdui-collapse> + {% else %} + <p>You haven't placed any orders yet.</p> + {% endif %} +{% endblock %} + diff --git a/store/templates/store/order_management.html b/store/templates/store/order_management.html new file mode 100644 index 0000000..18b6e4b --- /dev/null +++ b/store/templates/store/order_management.html @@ -0,0 +1,111 @@ +{% extends 'store/base.html' %} + +{% block content %} + <h2>Order Management</h2> + + <div style="overflow-x: auto; background-color: var(--mdui-color-surface); border-radius: 12px; border: 1px solid var(--mdui-color-outline-variant);"> + <table style="border-collapse: collapse; min-width: 800px;"> + <thead> + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); background-color: var(--mdui-color-surface-container);"> + <th style="padding: 1rem; text-align: center; width: 80px;">ID</th> + <th style="padding: 1rem; text-align: center; width: 120px;">Buyer</th> + <th style="padding: 1rem; text-align: left;">Address</th> + <th style="padding: 1rem; text-align: center; width: 180px;">Date</th> + <th style="padding: 1rem; text-align: center; width: 100px;">Total</th> + <th style="padding: 1rem; text-align: center; width: 120px;">Status</th> + <th style="padding: 1rem; text-align: center; width: 160px;">Actions</th> + <th style="padding: 1rem; text-align: center; width: 60px;"></th> + </tr> + </thead> + <tbody> + {% for order in orders %} + <tr style="border-bottom: 1px solid var(--mdui-color-outline-variant); transition: background-color 0.2s;" class="order-row"> + <td style="padding: 1rem; text-align: center;">#{{ order.id }}</td> + <td style="padding: 1rem; text-align: center;">{{ order.buyer.username }}</td> + <td style="padding: 1rem; text-align: left;"> + <div style="max-width: 250px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"> + {{ order.address }} + </div> + </td> + <td style="padding: 1rem; text-align: center;">{{ order.createdAt|date:"M d, Y H:i" }}</td> + <td style="padding: 1rem; text-align: center;">${{ order.total_price }}</td> + <td style="padding: 1rem; text-align: center;"> + {% if order.status == 1 %} + <span style="color: var(--mdui-color-secondary); font-weight: 500;">Pending</span> + {% elif order.status == 2 %} + <span style="color: var(--mdui-color-success); font-weight: 500;">Shipped</span> + {% elif order.status == 3 %} + <span style="color: var(--mdui-color-error); font-weight: 500;">Rejected</span> + {% endif %} + </td> + <td style="padding: 1rem; text-align: center;"> + <div style="display: flex; justify-content: center; align-items: center; gap: 0.5rem;"> + {% if order.status == 1 %} + <form action="{% url 'update_order_status' order.id %}" method="POST" style="display: inline-flex;"> + {% csrf_token %} + <input type="hidden" name="status" value="2"> + <mdui-button-icon type="submit" icon="local_shipping" title="Ship"></mdui-button-icon> + </form> + <form action="{% url 'update_order_status' order.id %}" method="POST" style="display: inline-flex;"> + {% csrf_token %} + <input type="hidden" name="status" value="3"> + <mdui-button-icon type="submit" icon="cancel" style="color: var(--mdui-color-error);" title="Reject"></mdui-button-icon> + </form> + {% else %} + <span style="font-size: 0.8rem; opacity: 0.7;">{{ order.get_status_display }}</span> + {% endif %} + </div> + </td> + <td style="padding: 1rem; text-align: center;"> + <mdui-button-icon icon="expand_more" onclick="toggleDetails({{ order.id }}, this)"></mdui-button-icon> + </td> + </tr> + <tr id="details-{{ order.id }}" style="display: none; background-color: var(--mdui-color-surface-container-low);"> + <td colspan="7" style="padding: 0;"> + <div style="padding: 1.5rem; border-bottom: 1px solid var(--mdui-color-outline-variant);"> + <h4 style="margin: 0 0 1rem 0;">Ordered Items</h4> + <table style="border-collapse: collapse; background-color: var(--mdui-color-surface); border-radius: 8px; overflow: hidden;"> + <thead> + <tr style="background-color: var(--mdui-color-surface-container); font-size: 0.85rem;"> + <th style="padding: 0.75rem; text-align: left;">Book</th> + <th style="padding: 0.75rem; text-align: center;">Price</th> + <th style="padding: 0.75rem; text-align: center;">Quantity</th> + <th style="padding: 0.75rem; text-align: right;">Subtotal</th> + </tr> + </thead> + <tbody> + {% for item in order.items.all %} + <tr style="border-bottom: 1px solid var(--mdui-color-surface-container-high);"> + <td style="padding: 0.75rem;">{{ item.book.title }}</td> + <td style="padding: 0.75rem; text-align: center;">${{ item.bookPrice }}</td> + <td style="padding: 0.75rem; text-align: center;">{{ item.amount }}</td> + <td style="padding: 0.75rem; text-align: right;">${{ item.total_price }}</td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +{% endblock %} + +{% block extra_script %} + <script> + function toggleDetails(id, btn) { + const row = document.getElementById('details-' + id); + if (row.style.display === 'none') { + row.style.display = 'table-row'; + btn.icon = 'expand_less'; + btn.closest('tr').style.backgroundColor = 'var(--mdui-color-surface-container-low)'; + } else { + row.style.display = 'none'; + btn.icon = 'expand_more'; + btn.closest('tr').style.backgroundColor = ''; + } + } + </script> +{% endblock %} diff --git a/store/templates/store/profile.html b/store/templates/store/profile.html new file mode 100644 index 0000000..d06cc7e --- /dev/null +++ b/store/templates/store/profile.html @@ -0,0 +1,39 @@ +{% extends 'store/base.html' %} + +{% block content %} +<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;"> + <mdui-card style="width: 100%; max-width: 600px; padding: 2rem;"> + <h2 style="margin-top: 0; margin-bottom: 2rem; text-align: center;">User Profile</h2> + + <form method="POST" enctype="multipart/form-data" id="profile-form"> + {% csrf_token %} + <div style="text-align: center; margin-bottom: 2rem; position: relative; cursor: pointer;" onclick="document.getElementById('avatar-input').click();"> + {% if user.avatar %} + <img src="{{ user.avatar.url }}" alt="Avatar" style="width: 150px; height: 150px; object-fit: cover; border-radius: 50%; box-shadow: var(--mdui-elevation-level1);"> + {% else %} + <div style="width: 150px; height: 150px; background-color: var(--mdui-color-secondary-container); color: var(--mdui-color-on-secondary-container); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 3rem;"> + {{ user.username|first|upper }} + </div> + {% endif %} + <div style="position: absolute; bottom: 5px; right: calc(50% - 65px); background: rgba(0,0,0,0.5); border-radius: 50%; padding: 5px;"> + <mdui-icon name="edit" style="color: white;"></mdui-icon> + </div> + </div> + <input type="file" name="avatar" id="avatar-input" style="display: none;" onchange="document.getElementById('profile-form').submit();"> + + <div style="display: grid; gap: 1rem;"> + <mdui-text-field label="Username" value="{{ user.username }}" disabled style="width: 100%;"></mdui-text-field> + <mdui-text-field label="Email" value="{{ user.email }}" disabled style="width: 100%;"></mdui-text-field> + + <mdui-text-field label="Full Name" name="name" value="{{ user.name|default:'' }}" style="width: 100%;"></mdui-text-field> + <mdui-text-field label="Phone" name="phone" value="{{ user.phone|default:'' }}" style="width: 100%;"></mdui-text-field> + <mdui-text-field label="Address" name="address" value="{{ user.address|default:'' }}" rows="3" style="width: 100%;"></mdui-text-field> + </div> + + <div style="margin-top: 2rem;"> + <mdui-button type="submit" full-width>Update Profile</mdui-button> + </div> + </form> + </mdui-card> +</div> +{% endblock %} diff --git a/store/templates/store/register.html b/store/templates/store/register.html new file mode 100644 index 0000000..3d68916 --- /dev/null +++ b/store/templates/store/register.html @@ -0,0 +1,49 @@ +{% extends 'store/base.html' %} +{% load static %} + +{% block content %} +<div style="display: flex; justify-content: center; margin-top: 3rem; margin-bottom: 3rem;"> + <mdui-card style="max-width: 500px; padding: 2rem;"> + <h2 style="margin-top: 0; text-align: center;">Register</h2> + <form method="POST"> + {% csrf_token %} + {% if form.non_field_errors %} + <div style="color: var(--mdui-color-error); margin-bottom: 1rem;"> + {% for error in form.non_field_errors %} + <p>{{ error }}</p> + {% endfor %} + </div> + {% endif %} + + {% for field in form %} + <div style="margin-bottom: 1rem;"> + <mdui-text-field + label="{{ field.label }}" + name="{{ field.name }}" + value="{{ field.value|default:'' }}" + {% if field.field.widget.input_type == 'password' %}type="password"{% endif %} + {% if field.field.required %}required{% endif %} + {% if field.name == 'address' %}rows="3"{% endif %} + > + {% if field.help_text %} + <span slot="helper">{{ field.help_text }}</span> + {% endif %} + </mdui-text-field> + {% if field.errors %} + <div style="color: var(--mdui-color-error); font-size: 0.875rem; margin-top: 0.25rem;"> + {% for error in field.errors %} + {{ error }} + {% endfor %} + </div> + {% endif %} + </div> + {% endfor %} + + <mdui-button type="submit" full-width icon="person_add">Register</mdui-button> + </form> + <div style="margin-top: 1rem; text-align: center;"> + <p>Already have an account? <a href="{% url 'login' %}" style="color: var(--mdui-color-primary);">Login</a></p> + </div> + </mdui-card> +</div> +{% endblock %} |
