First commit of POC version

This commit is contained in:
2026-01-01 21:07:34 +01:00
parent 3ea6e14d0e
commit 7ab655626a
65 changed files with 12372 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block content %}
<h4 class="mb-4">Pending Users</h4>
<ul class="list-group">
{% for user in users %}
<li class="list-group-item d-flex justify-content-between">
{{ user.name }}
<a class="btn btn-sm btn-success" href="/admin/approve/{{ user.id }}">Approve</a>
</li>
{% endfor %}
</ul>
{% endblock %}
+68
View File
@@ -0,0 +1,68 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title or "L@PP" }}</title>
<link href="/static/bootstrap.min.css" rel="stylesheet">
<script src="/static/jquery.min.js"></script>
<script src="/static/bootstrap.min.js"></script>
<style>
body {
padding-top: 64px;
}
.topbar {
height: 56px;
}
</style>
</head>
<body class="bg-light">
<!-- Top Bar -->
<nav class="navbar fixed-top navbar-light bg-white border-bottom topbar">
<div class="container-fluid">
<span class="navbar-brand fw-semibold">
<a class="btn btn-light" href="{{ logo_url or '/' }}" role="button">L@PP</a>
{% if button_top_url1 and button_top_txt1 %}
<a href="{{ button_top_url1 }}"><img style="width: 2em; height: 2em" src="/static/{{ button_top_txt1 }}.svg"></a>
{% endif %}
{% if button_top_url2 and button_top_txt2 %}
<a href="{{ button_top_url2 }}"><img style="width: 2em; height: 2em" src="/static/{{ button_top_txt2 }}.svg"></a>
{% endif %}
</span>
<div class="d-flex align-items-center gap-2">
{% if current_user.is_authenticated %}
<span class="text-muted small d-none d-sm-inline">
{{ current_user.name }}
</span>
<a href="{{ url_for('auth.logout') }}"
>
<img style="width: 2em; height: 2em" src="/static/logout.svg">
</a>
{% else %}
<a href="{{ url_for('auth.login') }}"
>
<img style="width: 2em; height: 2em" src="/static/login.svg"></a>
</a>
<a href="{{ url_for('auth.register') }}"
>
<img style="width: 2em; height: 2em" src="/static/register.svg"></a>
</a>
{% endif %}
</div>
</div>
</nav>
<!-- Main Content -->
<div class="container py-4">
<div class="mx-auto" style="max-width: 420px;">
{% block content %}{% endblock %}
</div>
</div>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
{% extends "base.html" %}
{% block content %}
<form method="POST">
{{ form.hidden_tag() }}
<div class="list">
<article>
{{ form.field1(class="") }}
{{ form.field2(class="") }}
{{ form.field3(class="") }}
{{ form.field4(class="") }}
</article>
</div>
<button type="submit" class="contrast">{{ form.submit.label }}</button>
</form>
{% endblock %}
+40
View File
@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block content %}
<table id="flowerlist">
{% for flower in flowers %}
<tr class="flowerlist">
<td class="flowerlist">
<a href="javascript:DoSubmit('{{flower[0]}}')">
{{ flower[1] }}, {{ flower[2] }}, {{ flower[3] }}
</a>
</td>
<td class="right"><span style="white-space: nowrap;">{{ flower[4][:10] }}</span></td>
</tr>
{% endfor %}
</table>
<form id="gotopage" action="{{ url_for('data-record-show') }}" method="POST">
<input type="hidden" id="_io" name="id" value="">
</form>
<script language="javascript">
var $rows = $('#flowerlist tr');
$('#search').keyup(function() {
var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
$rows.show().filter(function() {
var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
return !~text.indexOf(val);
}).hide();
});
$('#search').keyup();
function DoSubmit(id) {
$('#_id').val(id);
$('#gotopage').submit();
}
</script>
{% endblock %}
+12
View File
@@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block content %}
<h1>Hello, {{ current_user.username }}!</h1>
<p>Welcome to your modern minimal dashboard.</p>
{% if not current_user.is_authenticated %}
<p>
<a href="{{ url_for('login') }}">Login</a> or
<a href="{{ url_for('register') }}">Register</a>
</p>
{% endif %}
{% endblock %}
+101
View File
@@ -0,0 +1,101 @@
{% extends "base.html" %}
{% block content %}
<script>
let image_to_update = ''
// handle Delete
function handleDelete(button) {
window.location.href = '/item_delete/'+button.name ;
}
// handle AddOne
function handleAddOne(button) {
const response = fetch('/item_addone/'+button.name);
}
// handle Add with quantity
function handleAddWithQuantity(button, q) {
const response = fetch('/item_addquantity/'+button.name+'/'+q);
}
//trigger change category button
function clickedCategoryButton(image) {
image_to_update = image
}
// selected the category
function clickedUpdateCategory4Item(icon) {
image_to_update.src="{{ iconpath }}" + icon.name + ".svg"
const response = fetch('/item_update_category/'+image_to_update.name+'/'+icon.name);
$('#staticBackdrop').modal('hide');
}
$(document).ready(function(){
$("#myInput").on("keyup", function() {
var value = $(this).val().toLowerCase();
$("#myList tbody tr").filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(value) > -1)
});
});
});
</script>
<form method="post">
<div class=".container">
<div class="d-flex justify-content-between">
<input class="form-control" id="myInput" type="text" name="newItem" placeholder="Search.."/>
&nbsp;
<button class="btn btn-success">+</button>
</div>
</div>
</form>
<br/>
<table id="myList" style="width: 100%; background-color: white;">
<tbody>
{% for item in items %}
<tr style="border-bottom: 1px solid lightgray;">
<td style="padding: 5px;">
<a class="btn btn-{% if item.is_suggestion %}primary{% else %}info{% endif %} btn-sm" name="{{ item.id }}" onclick='handleAddOne(this);'>&nbsp;+&nbsp;</a>
{{ item.label }}</td>
<td style="text-align: right">
{% if item.quantity %}
<a class="btn btn-outline-primary btn-sm" name="{{ item.id }}" onclick='handleAddWithQuantity(this, {{ item.quantity }} );'>{{ item.quantity }}{{ item.unit }}</a>
&nbsp;&nbsp;&nbsp;
{% endif %}
<a href="" data-bs-toggle="modal" data-bs-target="#staticBackdrop"><img id="category4itemid_{{ item.id }}" name="{{item.id}}" src="{{ iconpath }}{{ item.category }}.svg" style="height: 2em;" alt="..." onclick='clickedCategoryButton(this);'></a>
<a class="btn btn-danger btn-sm" name="{{ item.id }}" onclick='handleDelete(this);'>-</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Modal -->
<div class="modal fade" id="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Category</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% for icon in icons %}
<a name= "{{ icon }}" onclick='clickedUpdateCategory4Item(this);'><img style="border: 10px solid white; height: 4em; width: 4em;" src="{{ iconpath }}{{ icon }}.svg" alt="..."></a>
{% endfor %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Exit</button>
</div>
</div>
</div>
</div>
{% endblock %}
+64
View File
@@ -0,0 +1,64 @@
{% extends "base.html" %}
{% block content %}
<script>
const IDLE_TIME = 10000; // 10 seconds of inactivity required after user activity
const MIN_REFRESH_GAP = 20000; // 20 seconds minimum between refreshes
function recordInteraction() {
// Reset idle timer
if (idleTimeout) {
clearTimeout(idleTimeout);
}
idleTimeout = setTimeout(checkAndRefresh, IDLE_TIME);
}
function checkAndRefresh() {
window.location.reload(true);
idleTimeout = setTimeout(checkAndRefresh, MIN_REFRESH_GAP);
}
// User activity listeners
["click", "touchstart", "keydown"].forEach(event => {
document.addEventListener(event, recordInteraction, { passive: true });
});
// Start idle timer on load
idleTimeout = setTimeout(checkAndRefresh, MIN_REFRESH_GAP);
document.addEventListener("visibilitychange", () => {
if (document.hidden && idleTimeout) clearTimeout(idleTimeout);
if (!document.hidden) checkAndRefresh();
});
// handle Checboc clicks
function handleCBClick(cb) {
const response = fetch('/item_update/'+cb.name+'/'+cb.checked);
}
</script>
<div class="list-group">
{% for item in items %}
<label class="list-group-item {% if item.is_checked %}text-muted{% endif %} d-flex justify-content-between">
<div>
<input class="form-check-input me-1" type="checkbox" value="" name="{{ item.id }}" {% if item.is_checked %}CHECKED{% endif %} onclick='handleCBClick(this);'>
{{ item.label }}
</div>
<div>
{% if item.quantity > 1 %}
{{ item.quantity }}{{ item.unit }}
{% endif %}
<img src="/static/categories/{{ item.category }}.svg" style="height: 2em;" alt="...">
</div>
</label>
{% endfor %}
</div>
{% endblock %}
+56
View File
@@ -0,0 +1,56 @@
{% extends "base.html" %}
{% block content %}
{% if error_id %}
<div class="alert alert-danger" role="alert">
You gave a duplicate name.
</div>
{% endif %}
<ul class="list-group">
{% for il in ilists %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<form method="post">
<input class="form-control mb-3{% if error_id==il.id %} is-invalid{% endif %}" name="name" value="{{ il.name }}" required>
<input type="hidden" name="iid" value="{{ il.id }}">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="{{ il.is_active }}" name="is_active" {% if il.is_active %}CHECKED{% endif %}>
<label class="form-check-label" for="is_active">
Is active
</label>
</div>
{% for u in users %}
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" name="shares_{{ u.id }}" value="{{ u.name }}" {% if u.shares_in_list(il.id) %}CHECKED{% endif %}>
<label class="form-check-label" for="inlineCheckbox1">{{ u.name }}</label>
</div>
{% endfor %}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="to_be_deleted">
<label class="form-check-label" for="to_be_deleted">
Must be deleted
</label>
</div>
<input type="hidden" name="owner_user_id" value="{{ il.owner_user_id }}">
<input type="image" src="/static/save.svg" alt="Save" style="width: 2em; height: 2em;">
</form>
</li>
{% endfor %}
</ul>
<br/>
New:<br/>
<ul class="list-group">
<li class="list-group-item d-flex justify-content-between align-items-center">
<form method="post">
<input class="form-control mb-3{% if error_id==9999 %} is-invalid{% endif %}" name="name" placeholder="{{ newlist.name }}" required>
<input type="image" src="/static/save.svg" alt="Save" style="width: 2em; height: 2em;">
</form>
</li>
</ul>
{{ error_id }}
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block content %}
<div class="list-group">
{% for il in ilists %}
<a class="list-group-item d-flex justify-content-between align-items-center" href="/items/{{ il.id }}">
{{ il.name }}
<span class="badge bg-primary rounded-pill">{{ il.items_left() }} / {{ il.items_total() }}</span>
</a>
{% endfor %}
</div>
<br/>
<a href="{{ url_for('lists.edit') }}"><img style="width: 3em; height: 3em" src="/static/edit.svg"></a>
{% endblock %}
+15
View File
@@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block content %}
<h3 class="text-center mb-4"><img style="width: 3em; height: 3em" src="/static/login.svg"></h3>
<form method="post">
<input class="form-control mb-3" name="name" placeholder="Name" required>
<input class="form-control mb-3" name="password" type="password" placeholder="Password" required>
<input
type="image"
src="/static/save.svg"
alt="Save"
style="width: 2em; height: 2em;"
>
</form>
{% endblock %}
+6
View File
@@ -0,0 +1,6 @@
{% extends "base.html" %}
{% block content %}
<div class="alert alert-info text-center">
Your account is pending admin approval.
</div>
{% endblock %}
+14
View File
@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block content %}
<h3 class="text-center mb-4"><img style="width: 3em; height: 3em" src="/static/register.svg"></h3>
<form method="post">
<input class="form-control mb-3" name="name" placeholder="New Name" required>
<input class="form-control mb-3" name="password" type="password" placeholder="New Password" required>
<input
type="image"
src="/static/save.svg"
alt="Save"
style="width: 2em; height: 2em;"
>
</form>
{% endblock %}
+34
View File
@@ -0,0 +1,34 @@
{% extends "base.html" %}
{% block content %}
<h2>Secure Data</h2>
<form method="POST">
{{ form.hidden_tag() }}
<div class="grid">
<article>
<label>{{ form.field1.label }}</label>
{{ form.field1(class="") }}
</article>
<article>
<label>{{ form.field2.label }}</label>
{{ form.field2(class="") }}
</article>
<article>
<label>{{ form.field3.label }}</label>
{{ form.field3(class="") }}
</article>
<article>
<label>{{ form.field4.label }}</label>
{{ form.field4(class="") }}
</article>
</div>
<button type="submit" class="contrast">{{ form.submit.label }}</button>
</form>
{% endblock %}
+30
View File
@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
<h2>User Management</h2>
<div class="grid">
{% for u in users %}
<article>
<header>
<h3>{{ u.name }}</h3>
<p>ID: {{ u.id }}</p>
</header>
<p><strong>Role:</strong> {{ u.role }}</p>
<footer>
{% if not u.is_admin %}
<a href="{{ url_for('user_admit', user_id=u.id) }}">Promote to Admin</a>
{% else %}
<span>Admin</span>
{% endif %}
|
<a href="{{ url_for('user_delete', user_id=u.id) }}"
onclick="return confirm('Delete {{ u.username }}?');">
Delete
</a>
</footer>
</article>
{% endfor %}
</div>
{% endblock %}