moved the report button

This commit is contained in:
2026-07-17 18:19:42 +02:00
parent 2cda77b9a5
commit 9d30359602
5 changed files with 55 additions and 42 deletions
-1
View File
@@ -3,4 +3,3 @@ __pycache__/
.pytest_cache/ .pytest_cache/
*.py[cod] *.py[cod]
config.yaml config.yaml
instance/
Binary file not shown.
+9 -2
View File
@@ -178,7 +178,6 @@ button { -webkit-tap-highlight-color: transparent; }
.amount-input span { padding-left: 9px; font-size: 13px; } .amount-input span { padding-left: 9px; font-size: 13px; }
.amount-input input { width: 100%; min-width: 0; height: 100%; padding: 0 8px 0 3px; border: 0; outline: 0; background: transparent; color: var(--ink); font-size: 16px; } .amount-input input { width: 100%; min-width: 0; height: 100%; padding: 0 8px 0 3px; border: 0; outline: 0; background: transparent; color: var(--ink); font-size: 16px; }
.custom-amount-form > button { padding: 0 13px; } .custom-amount-form > button { padding: 0 13px; }
.report-row { display: flex; justify-content: flex-end; margin-bottom: 7px; }
.delete-row { display: flex; justify-content: flex-end; margin-top: 7px; } .delete-row { display: flex; justify-content: flex-end; margin-top: 7px; }
.report-button { .report-button {
display: flex; display: flex;
@@ -194,7 +193,15 @@ button { -webkit-tap-highlight-color: transparent; }
font-weight: 800; font-weight: 800;
text-decoration: none; text-decoration: none;
} }
.report-row .report-button { min-width: 92px; } .calendar-header .report-button {
min-height: 36px;
padding: 0 13px;
border-color: var(--green);
border-radius: 999px;
background: transparent;
color: var(--green);
font-size: 12px;
}
.delete-row button { .delete-row button {
min-height: 28px; min-height: 28px;
padding: 0 10px; padding: 0 10px;
+1 -3
View File
@@ -9,6 +9,7 @@
<h1 id="visible-month">Calendar</h1> <h1 id="visible-month">Calendar</h1>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<a class="report-button" href="{{ url_for('report') }}">Report</a>
<button class="today-button" data-today-button type="button">Today</button> <button class="today-button" data-today-button type="button">Today</button>
<form action="{{ url_for('logout') }}" method="post"> <form action="{{ url_for('logout') }}" method="post">
<button class="logout-button" type="submit" aria-label="Sign out">Sign out</button> <button class="logout-button" type="submit" aria-label="Sign out">Sign out</button>
@@ -25,9 +26,6 @@
<section class="detail-panel" aria-live="polite"> <section class="detail-panel" aria-live="polite">
<p id="selected-date" class="selected-date">Tap a day</p> <p id="selected-date" class="selected-date">Tap a day</p>
<div class="report-row">
<a class="report-button" href="{{ url_for('report') }}">Report</a>
</div>
<div id="amount-controls" class="amount-controls" hidden> <div id="amount-controls" class="amount-controls" hidden>
<div class="status-options" aria-label="Choose day status"> <div class="status-options" aria-label="Choose day status">
<button type="button" data-status="worked">Worked</button> <button type="button" data-status="worked">Worked</button>
+45 -36
View File
@@ -1,9 +1,15 @@
import tempfile import tempfile
from fatima import create_app from flask_fatima import create_app
URL_PREFIX = "/fatima"
def client(url_prefix="/fatima"): def url(path="/"):
return f"{URL_PREFIX}{path}"
def client(url_prefix=URL_PREFIX):
database = tempfile.NamedTemporaryFile(suffix=".sqlite") database = tempfile.NamedTemporaryFile(suffix=".sqlite")
app = create_app({ app = create_app({
"TESTING": True, "TESTING": True,
@@ -19,26 +25,29 @@ def client(url_prefix="/fatima"):
def test_calendar_requires_login(): def test_calendar_requires_login():
response = client().get("/fatima/") response = client().get(url())
assert response.status_code == 302 assert response.status_code == 302
assert response.headers["Location"].endswith("/fatima/login") assert response.headers["Location"].endswith(url("/login"))
def test_prefix_applies_to_generated_links_and_static_assets(): def test_prefix_applies_to_generated_links_and_unprefixed_routes_are_rejected():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
page = test_client.get("/fatima/") page = test_client.get(url())
assert b'href="/fatima/static/styles.css"' in page.data assert b'href="/fatima/static/styles.css"' in page.data
assert b'src="/fatima/static/calendar.js"' in page.data assert b'src="/fatima/static/calendar.js"' in page.data
assert b'data-save-url="/fatima/api/choices"' in page.data assert b'data-save-url="/fatima/api/choices"' in page.data
assert b'href="/fatima/report"' in page.data
assert page.data.index(b'class="report-button"') < page.data.index(b'class="today-button"')
assert test_client.get("/").status_code == 404 assert test_client.get("/").status_code == 404
def test_url_prefix_is_configurable_and_can_be_disabled(): def test_url_prefix_is_configurable_and_can_be_disabled():
custom_client = client("/calendar") custom_client = client("/calendar")
assert custom_client.get("/calendar/").headers["Location"].endswith("/calendar/login") response = custom_client.get("/calendar/")
assert custom_client.get("/fatima/").status_code == 404 assert response.headers["Location"].endswith("/calendar/login")
assert custom_client.get(url()).status_code == 404
root_client = client("") root_client = client("")
assert root_client.get("/").headers["Location"].endswith("/login") assert root_client.get("/").headers["Location"].endswith("/login")
@@ -46,52 +55,52 @@ def test_url_prefix_is_configurable_and_can_be_disabled():
def test_login_and_logout(): def test_login_and_logout():
test_client = client() test_client = client()
response = test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) response = test_client.post(url("/login"), data={"username": "user", "password": "pass"})
assert response.status_code == 302 assert response.status_code == 302
assert response.headers["Location"].endswith("/fatima/") assert response.headers["Location"].endswith(url())
assert test_client.get("/fatima/").status_code == 200 assert test_client.get(url()).status_code == 200
assert test_client.post("/fatima/logout").headers["Location"].endswith("/fatima/login") assert test_client.post(url("/logout")).headers["Location"].endswith(url("/login"))
def test_bad_login_shows_error(): def test_bad_login_shows_error():
response = client().post("/fatima/login", data={"username": "user", "password": "wrong"}) response = client().post(url("/login"), data={"username": "user", "password": "wrong"})
assert response.status_code == 200 assert response.status_code == 200
assert b"not correct" in response.data assert b"not correct" in response.data
def test_choice_is_saved_and_rendered(): def test_choice_is_saved_and_rendered():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
response = test_client.post("/fatima/api/choices", json={"date": "2026-07-15", "amount": "27.50"}) response = test_client.post(url("/api/choices"), json={"date": "2026-07-15", "amount": "27.50"})
assert response.status_code == 200 assert response.status_code == 200
assert response.json["amount_cents"] == 2750 assert response.json["amount_cents"] == 2750
assert b'"2026-07-15": 2750' in test_client.get("/fatima/").data assert b'"2026-07-15": 2750' in test_client.get(url()).data
def test_choice_rejects_invalid_amount(): def test_choice_rejects_invalid_amount():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
response = test_client.post("/fatima/api/choices", json={"date": "2026-07-15", "amount": "-1"}) response = test_client.post(url("/api/choices"), json={"date": "2026-07-15", "amount": "-1"})
assert response.status_code == 400 assert response.status_code == 400
def test_day_status_is_saved_and_rendered(): def test_day_status_is_saved_and_rendered():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
response = test_client.post( response = test_client.post(
"/fatima/api/statuses", url("/api/statuses"),
json={"date": "2026-07-15", "status": "vacation"}, json={"date": "2026-07-15", "status": "vacation"},
) )
assert response.status_code == 200 assert response.status_code == 200
assert response.json["status"] == "vacation" assert response.json["status"] == "vacation"
assert b'"2026-07-15": "vacation"' in test_client.get("/fatima/").data assert b'"2026-07-15": "vacation"' in test_client.get(url()).data
def test_day_status_rejects_unknown_value(): def test_day_status_rejects_unknown_value():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
response = test_client.post( response = test_client.post(
"/fatima/api/statuses", url("/api/statuses"),
json={"date": "2026-07-15", "status": "elsewhere"}, json={"date": "2026-07-15", "status": "elsewhere"},
) )
assert response.status_code == 400 assert response.status_code == 400
@@ -99,14 +108,14 @@ def test_day_status_rejects_unknown_value():
def test_delete_removes_amount_and_status(): def test_delete_removes_amount_and_status():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
test_client.post("/fatima/api/choices", json={"date": "2026-07-15", "amount": "55"}) test_client.post(url("/api/choices"), json={"date": "2026-07-15", "amount": "55"})
test_client.post("/fatima/api/statuses", json={"date": "2026-07-15", "status": "sick"}) test_client.post(url("/api/statuses"), json={"date": "2026-07-15", "status": "sick"})
response = test_client.post("/fatima/api/delete-date", json={"date": "2026-07-15"}) response = test_client.post(url("/api/delete-date"), json={"date": "2026-07-15"})
assert response.status_code == 200 assert response.status_code == 200
assert response.json["deleted"] is True assert response.json["deleted"] is True
page = test_client.get("/fatima/").data page = test_client.get(url()).data
assert b'"2026-07-15": 5500' not in page assert b'"2026-07-15": 5500' not in page
assert b'"2026-07-15": "sick"' not in page assert b'"2026-07-15": "sick"' not in page
@@ -115,15 +124,15 @@ def test_report_groups_money_and_days_by_status():
from datetime import date from datetime import date
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
year = date.today().year year = date.today().year
worked_date = f"{year}-01-10" worked_date = f"{year}-01-10"
vacation_date = f"{year}-02-10" vacation_date = f"{year}-02-10"
test_client.post("/fatima/api/choices", json={"date": worked_date, "amount": "55"}) test_client.post(url("/api/choices"), json={"date": worked_date, "amount": "55"})
test_client.post("/fatima/api/choices", json={"date": vacation_date, "amount": "27.50"}) test_client.post(url("/api/choices"), json={"date": vacation_date, "amount": "27.50"})
test_client.post("/fatima/api/statuses", json={"date": vacation_date, "status": "vacation"}) test_client.post(url("/api/statuses"), json={"date": vacation_date, "status": "vacation"})
response = test_client.get("/fatima/report") response = test_client.get(url("/report"))
assert response.status_code == 200 assert response.status_code == 200
assert b"\xe2\x82\xac55.00" in response.data assert b"\xe2\x82\xac55.00" in response.data
assert b"\xe2\x82\xac27.50" in response.data assert b"\xe2\x82\xac27.50" in response.data
@@ -132,8 +141,8 @@ def test_report_groups_money_and_days_by_status():
def test_report_can_navigate_to_another_year(): def test_report_can_navigate_to_another_year():
test_client = client() test_client = client()
test_client.post("/fatima/login", data={"username": "user", "password": "pass"}) test_client.post(url("/login"), data={"username": "user", "password": "pass"})
response = test_client.get("/fatima/report?year=2025") response = test_client.get(url("/report?year=2025"))
assert response.status_code == 200 assert response.status_code == 200
assert b"2025 report" in response.data assert b"2025 report" in response.data
assert b"year=2024" in response.data assert b"year=2024" in response.data