Add web interface
This commit is contained in:
parent
a1351f5187
commit
cb8b6ab4fa
35
app.py
35
app.py
@ -29,6 +29,41 @@ def submit_isbn():
|
||||
return f"{book.title} ajouté (plusieurs occurrences)"
|
||||
return f"{book.title} ajouté"
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html", books=isbn_db.get_all_books())
|
||||
|
||||
|
||||
@app.route("/delete-book", methods=["POST"])
|
||||
def delete_book():
|
||||
if "isbn" not in request.form:
|
||||
return "missing isbn"
|
||||
|
||||
isbn_db.delete_book(request.form["isbn"])
|
||||
return redirect("/")
|
||||
|
||||
@app.route("/update-book", methods=["POST"])
|
||||
def update_book():
|
||||
attributes = ["isbn", "count", "title", "author", "publisher", "publish_date"]
|
||||
if True in [i not in request.form for i in attributes]:
|
||||
return "missing an attribute"
|
||||
|
||||
form_data = request.form.copy()
|
||||
|
||||
for attribute in attributes:
|
||||
if form_data[attribute] == "None":
|
||||
form_data[attribute] = None
|
||||
|
||||
book = Book(form_data["isbn"])
|
||||
book._manual_load(
|
||||
form_data["title"],
|
||||
publisher=form_data["publisher"],
|
||||
publish_date=form_data["publish_date"],
|
||||
author=form_data["author"],
|
||||
count=int(form_data["count"])
|
||||
)
|
||||
isbn_db.update_book(book)
|
||||
return redirect("/")
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
|
2
book.py
2
book.py
@ -7,7 +7,7 @@ class Book:
|
||||
return None
|
||||
|
||||
try:
|
||||
isbn = isbn.replace("-", "")
|
||||
isbn = str(isbn).replace("-", "")
|
||||
int(isbn)
|
||||
except ValueError:
|
||||
raise ValueError("ISBN must be an int")
|
||||
|
57
isbn_db.py
57
isbn_db.py
@ -40,6 +40,17 @@ def get_book(isbn):
|
||||
)
|
||||
return book
|
||||
|
||||
def delete_book(isbn):
|
||||
db = get_db()
|
||||
|
||||
data = db.execute(
|
||||
"""
|
||||
DELETE FROM book WHERE isbn=?
|
||||
""",
|
||||
(isbn,)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
def increment_count(book):
|
||||
if book.count == -1:
|
||||
book = get_book(book.isbn)
|
||||
@ -74,3 +85,49 @@ def add_book(book):
|
||||
db.commit()
|
||||
return "added"
|
||||
|
||||
def update_book(book):
|
||||
db = get_db()
|
||||
db.execute(
|
||||
"""
|
||||
UPDATE book SET count=?, title=?, author=?, publisher=?, publish_date=?
|
||||
WHERE isbn=?
|
||||
""",
|
||||
(book.count, book.title, book.author, book.publisher, book.publish_date, book.isbn)
|
||||
)
|
||||
db.commit()
|
||||
return "updated"
|
||||
|
||||
def get_all_books():
|
||||
def count_none(book):
|
||||
count = 0
|
||||
if book.title is None:
|
||||
count += 1
|
||||
if book.author is None:
|
||||
count += 1
|
||||
if book.publish_date is None:
|
||||
count += 1
|
||||
if book.publisher is None:
|
||||
count += 1
|
||||
return -count
|
||||
|
||||
db = get_db()
|
||||
|
||||
data = db.execute(
|
||||
"""
|
||||
SELECT * FROM book
|
||||
"""
|
||||
).fetchall()
|
||||
|
||||
books = []
|
||||
for data_row in data:
|
||||
book = Book(data_row["isbn"])
|
||||
book._manual_load(
|
||||
data_row["title"],
|
||||
publisher=data_row["publisher"],
|
||||
publish_date=data_row["publish_date"],
|
||||
author=data_row["author"],
|
||||
count=data_row["count"]
|
||||
)
|
||||
books.append(book)
|
||||
|
||||
return sorted(books, key=count_none)
|
82
static/main.js
Normal file
82
static/main.js
Normal file
@ -0,0 +1,82 @@
|
||||
getBookData = (isbn) => {
|
||||
var table = document.getElementById("books-table");
|
||||
|
||||
// Find all rows in the table except the header
|
||||
var rows = table.getElementsByTagName("tr");
|
||||
|
||||
// Iterate through each row
|
||||
for (var i = 1; i < rows.length; i++) { // starting from 1 to skip the header row
|
||||
var cells = rows[i].getElementsByTagName("td");
|
||||
|
||||
// Check if the ISBN in the current row matches the given ISBN
|
||||
if (cells[0].innerText == isbn) {
|
||||
// Extract data from the row
|
||||
var title = cells[1].innerText;
|
||||
var author = cells[2].innerText;
|
||||
var date = cells[3].innerText;
|
||||
var publisher = cells[4].innerText;
|
||||
var quantity = cells[5].innerText;
|
||||
|
||||
// Return the data
|
||||
return {
|
||||
title: title,
|
||||
author: author,
|
||||
date: date,
|
||||
publisher: publisher,
|
||||
quantity: quantity
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If ISBN is not found, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
function edit_book(isbn) {
|
||||
var bookData = getBookData(isbn);
|
||||
if (bookData) {
|
||||
var editDialog = document.getElementById("edit-book-dialog");
|
||||
document.getElementById("edit-isbn").value = isbn;
|
||||
document.getElementById("edit-title").value = bookData.title;
|
||||
document.getElementById("edit-author").value = bookData.author;
|
||||
document.getElementById("edit-date").value = bookData.date;
|
||||
document.getElementById("edit-publisher").value = bookData.publisher;
|
||||
document.getElementById("edit-quantity").value = bookData.quantity;
|
||||
editDialog.showModal();
|
||||
} else {
|
||||
alert("Book not found!");
|
||||
}
|
||||
}
|
||||
|
||||
// Function to hide the edit-book dialog
|
||||
function hideEditBookDialog() {
|
||||
var editDialog = document.getElementById("edit-book-dialog");
|
||||
editDialog.close();
|
||||
}
|
||||
|
||||
// Function to show the delete-book dialog
|
||||
function delete_book(isbn) {
|
||||
var bookData = getBookData(isbn);
|
||||
console.log(isbn, bookData)
|
||||
if (bookData === null)
|
||||
return;
|
||||
|
||||
var deleteDialog = document.getElementById("delete-book-dialog");
|
||||
document.getElementById("delete-isbn").value = isbn;
|
||||
document.getElementById("delete-book-name").innerText = bookData.title;
|
||||
if (bookData.title === undefined || bookData.title == "None")
|
||||
document.getElementById("delete-book-name").innerText = "ISBN:"+isbn;
|
||||
|
||||
|
||||
deleteDialog.showModal();
|
||||
// Handle cancel delete
|
||||
document.getElementById("cancel-delete").onclick = function() {
|
||||
deleteDialog.close();
|
||||
};
|
||||
}
|
||||
|
||||
// Function to hide the delete-book dialog
|
||||
function hideDeleteBookDialog() {
|
||||
var deleteDialog = document.getElementById("delete-book-dialog");
|
||||
deleteDialog.close();
|
||||
}
|
274
static/style.css
Normal file
274
static/style.css
Normal file
@ -0,0 +1,274 @@
|
||||
@import url('/static/font/iosevka.css');
|
||||
|
||||
/** Color Schemes */
|
||||
/* Themes used: Catppuccin Latte & Moccha
|
||||
* https://github.com/catppuccin/catppuccin */
|
||||
|
||||
/* Dark theme: Catpuccin Mocha */
|
||||
:root {
|
||||
--color-rosewater: #f5e0dc;
|
||||
--color-flamingo: #f2cdcd;
|
||||
--color-pink: #f5c2e7;
|
||||
--color-mauve: #cba6f7;
|
||||
--color-red: #f38ba8;
|
||||
--color-maroon: #eba0ac;
|
||||
--color-peach: #fab387;
|
||||
--color-yellow: #f9e2af;
|
||||
--color-green: #a6e3a1;
|
||||
--color-teal: #94e2d5;
|
||||
--color-sky: #89dceb;
|
||||
--color-sapphire: #74c7ec;
|
||||
--color-blue: #89b4fa;
|
||||
--color-lavender: #b4befe;
|
||||
--color-text: #cdd6f4;
|
||||
--color-subtext1: #bac2de;
|
||||
--color-subtext0: #a6adc8;
|
||||
--color-overlay2: #9399b2;
|
||||
--color-overlay1: #7f849c;
|
||||
--color-overlay0: #6c7086;
|
||||
--color-surface2: #585b70;
|
||||
--color-surface1: #45475a;
|
||||
--color-surface0: #313244;
|
||||
--color-base: #1e1e2e;
|
||||
--color-mantle: #181825;
|
||||
--color-crust: #11111b;
|
||||
--font-family: Iosevka Web;
|
||||
}
|
||||
|
||||
/* Light theme: Catppuccin Latte */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-rosewater: #dc8a78;
|
||||
--color-flamingo: #dd7878;
|
||||
--color-pink: #ea76cb;
|
||||
--color-mauve: #8839ef;
|
||||
--color-red: #d20f39;
|
||||
--color-maroon: #e64553;
|
||||
--color-peach: #fe640b;
|
||||
--color-yellow: #df8e1d;
|
||||
--color-green: #40a02b;
|
||||
--color-teal: #179299;
|
||||
--color-sky: #04a5e5;
|
||||
--color-sapphire: #209fb5;
|
||||
--color-blue: #1e66f5;
|
||||
--color-lavender: #7287fd;
|
||||
--color-text: #4c4f69;
|
||||
--color-subtext1: #5c5f77;
|
||||
--color-subtext0: #6c6f85;
|
||||
--color-overlay2: #7c7f93;
|
||||
--color-overlay1: #8c8fa1;
|
||||
--color-overlay0: #9ca0b0;
|
||||
--color-surface2: #acb0be;
|
||||
--color-surface1: #bcc0cc;
|
||||
--color-surface0: #ccd0da;
|
||||
--color-base: #eff1f5;
|
||||
--color-mantle: #e6e9ef;
|
||||
--color-crust: #dce0e8;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
#stats {
|
||||
display: flex;
|
||||
background-color: var(--color-crust);
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#stats-used {
|
||||
margin-left: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#stats-available {
|
||||
margin-left: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.stats-block {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 3px;
|
||||
background-color: var(--color-base);
|
||||
display: flex;
|
||||
|
||||
height: 40px;
|
||||
padding: 0 15px;
|
||||
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
color: var(--color-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-base);
|
||||
color: var(--color-text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#logout {
|
||||
margin-bottom: 5px;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
#actions {
|
||||
margin-top: 5%;
|
||||
}
|
||||
|
||||
#add-voucher {
|
||||
margin-bottom: 5%;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
input {
|
||||
color: inherit;
|
||||
background-color: var(--color-mantle);
|
||||
border-radius: 3px;
|
||||
border-style: solid;
|
||||
border-color: var(--color-overlay0);
|
||||
margin-bottom: .2em;
|
||||
font-family: inherit;
|
||||
width: -moz-available;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
input:placeholder-shown {
|
||||
font-style: oblique;
|
||||
}
|
||||
|
||||
input:hover {
|
||||
background-color: var(--color-crust);
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.barcode {
|
||||
width: 80%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.table-barcode.barcode {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.voucher {
|
||||
background-color: var(--color-crust);
|
||||
border-radius: 5px;
|
||||
margin-bottom: 30px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
color: inherit;
|
||||
background-color: var(--color-crust);
|
||||
border-color: var(--color-crust);
|
||||
border-style: solid;
|
||||
border-radius: 3px;
|
||||
border-width: 2px;
|
||||
padding: 5px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: var(--color-lavender);
|
||||
}
|
||||
|
||||
.flash {
|
||||
margin: 1em;
|
||||
padding: 1em;
|
||||
background: var(--color-mantle);
|
||||
border: 2px solid var(--color-lavender);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.stats-block:hover {
|
||||
background-color: var(--color-lavender);
|
||||
color: var(--color-crust);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
pre {
|
||||
background-color: var(--color-mantle);
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
/** Style for table: alternate colors */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
border-color: var(--color-crust);
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.action {
|
||||
padding: 7px;
|
||||
margin: 3px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 2px 2px rgba(0, 0, 0, 0.2);
|
||||
background-color: #cdd6f4;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--color-crust);
|
||||
}
|
||||
|
||||
table tr:nth-child(odd) td{
|
||||
background-color: var(--color-mantle);
|
||||
}
|
||||
table tr:nth-child(even) td{
|
||||
background-color: var(--color-base);
|
||||
}
|
||||
|
||||
dialog {
|
||||
/* Geometry */
|
||||
border-width: 2px;
|
||||
border-radius: 3px;
|
||||
|
||||
/* Colors */
|
||||
background-color: var(--color-crust);
|
||||
color: var(--color-text);
|
||||
|
||||
min-width: 75vw;
|
||||
}
|
24
templates/base.html
Normal file
24
templates/base.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="description" content="{{ self.title() }}" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#eff1f5">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#1e1e2e">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<script src="{{ url_for('static', filename='main.js') }}" defer></script>
|
||||
<div id="container">
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
<section id="content">
|
||||
{% block content %}{% endblock %}
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
61
templates/index.html
Normal file
61
templates/index.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Table{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<h1>Table des livres</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<dialog id="edit-book-dialog">
|
||||
<form id="edit-book-form" action="/update-book" method="post">
|
||||
<input type="hidden" id="edit-isbn" value="" name="isbn">
|
||||
<label for="edit-title">Titre:</label><br>
|
||||
<input type="text" id="edit-title" name="title"><br>
|
||||
<label for="edit-author">Auteur:</label><br>
|
||||
<input type="text" id="edit-author" name="author"><br>
|
||||
<label for="edit-date">Date:</label><br>
|
||||
<input type="text" id="edit-date" name="publish_date"><br>
|
||||
<label for="edit-publisher">Éditeur:</label><br>
|
||||
<input type="text" id="edit-publisher" name="publisher"><br>
|
||||
<label for="edit-quantity">Quantité:</label><br>
|
||||
<input type="number" id="edit-quantity" name="count"><br>
|
||||
<button type="submit">Mettre à jour</button>
|
||||
</form>
|
||||
<button onclick="hideEditBookDialog()">Annuler</button>
|
||||
</dialog>
|
||||
<dialog id="delete-book-dialog">
|
||||
<p>Êtes-vous sûr de supprimer ce livre ?</p>
|
||||
<b id="delete-book-name">Nom du livre...</b>
|
||||
<form id="delete-book-form" action="/delete-book" method="post">
|
||||
<input type="hidden" id="delete-isbn" value="" name="isbn">
|
||||
<button type="submit">Oui</button>
|
||||
</form>
|
||||
<button id="cancel-delete">Annuler</button>
|
||||
</dialog>
|
||||
<table id="books-table">
|
||||
<tr>
|
||||
<th>ISBN</th>
|
||||
<th>Titre</th>
|
||||
<th>Auteur</th>
|
||||
<th>Date</th>
|
||||
<th>Éditeur</th>
|
||||
<th>Quantité</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{% for book in books %}
|
||||
<tr>
|
||||
<td>{{ book.isbn }}</td>
|
||||
<td><p {% if book.title == None %}class="red"{% endif %}>{{ book.title }}</p></td>
|
||||
<td><p {% if book.author == None %}class="red"{% endif %}>{{ book.author }}</p></td>
|
||||
<td><p {% if book.publish_date == None %}class="red"{% endif %}>{{ book.publish_date }}</p></td>
|
||||
<td><p {% if book.publisher == None %}class="red"{% endif %}>{{ book.publisher }}</p></td>
|
||||
<td>{{ book.count }}</td>
|
||||
<td>
|
||||
<button class="action" onclick="edit_book({{ book.isbn }})">✏️</button>
|
||||
<button class="action" onclick="delete_book({{ book.isbn }})">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user