diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8231859..36d0e22 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -16,6 +16,22 @@ Numbers like #24 are GitHub issues or pull requests on [vives-project-xp/Project Newest first. Each request has the same four parts: **Before**, **Asked**, **Changed**, **Where**. +### R13. Tables fit the screen (2026-10-22, #89, PR #90) + +**Before.** Page content was at most 896 px wide, and table cells never wrapped. The **Users** table (Name, Email, GitHub, Role, Programme, Year, Status) was wider than that, so on a laptop you had to **scroll to the right** to see Year and Status. + +**Asked.** *"change users table so it fits the whole screen, we have to go to the right to view year and status good, make it autoscale"*. + +**Changed.** +- **Wider pages:** the content of every page (and the footer) can now be up to 1280 px wide. Forms and cards keep their narrow width; tables and lists use the extra room. +- **Tables adapt to the screen:** + - table cells wrap between words; + - email addresses and GitHub names may break anywhere. + + Every table now fits without scrolling sideways. At 1366 px the Users table fits with one line per row. In an 800 px window it fits too: only the emails and GitHub names break over lines, and words stay whole. + +**Where.** `frontend/src/pmp_frontend/theme.py` (`CONTENT_WIDTH`, `BREAK_ANYWHERE`, table CSS), `users_page.py`; test in `frontend/tests/test_navigation.py`. + ### R12. Tidy the buttons on the Projects screen (2026-10-22, #81, PR #85) **Before.** The Projects screen had the heading and **five buttons on one row**: "Create repos for all Projects", "Check all members", "Create Trello boards for all Projects", "Check all Trello members" and "New Project". On a normal screen they wrapped onto several lines and looked cluttered. On a Project's page, the status, the Archive and Delete buttons and the "Open for choice" switch were **loose under the title**, and in the Members card the "Add a Student" select and the "Add Member" button were on separate lines. @@ -225,6 +241,7 @@ Newest first. "Live" means deployed to the VIVES VM by the Deploy workflow. "Mig |---|---|---|---| | Changed | Footer links and address from the environment (`FOOTER_*`) | #79 | #83 | | New | Top 3 screen: add a Student to a Project outside their Top 3 | #80 | #84 | +| Changed | Tables fit the screen: wider pages (1280 px), cells wrap, emails break (R13) | #89 | #90 | | Changed | Projects screen: GitHub ▾ / Trello ▾ menus; Status card on a Project's page | #81 | #85 | | New | This changelog (`docs/CHANGELOG.md`) | #82 | this PR | diff --git a/frontend/src/pmp_frontend/theme.py b/frontend/src/pmp_frontend/theme.py index 1e20dae..a9819bf 100644 --- a/frontend/src/pmp_frontend/theme.py +++ b/frontend/src/pmp_frontend/theme.py @@ -15,6 +15,12 @@ STATIC_DIR = Path(__file__).parent / "static" +# Wide enough for the tables on a laptop screen (#89); forms keep their own +# narrow cards. +CONTENT_WIDTH = "max-w-7xl" +# Table column classes for long values without spaces, such as email addresses. +BREAK_ANYWHERE = "break-anywhere" + # The footer's social links; their URLs come from the settings (#79). SOCIALS = ["Instagram", "TikTok", "YouTube", "LinkedIn"] # Bootstrap Icons (MIT, see static/icons/LICENSE-bootstrap-icons.txt), inlined so @@ -32,6 +38,10 @@ font-family: 'Poppins', Arial, sans-serif; color: {BLACK}; background: {SAND}; }} b, strong, h1, h2, h3, h4, .text-bold {{ font-weight: 600 !important; }} + /* Tables fit the screen instead of scrolling sideways (#89): cells wrap + between words; columns with BREAK_ANYWHERE (email addresses) anywhere. */ + .q-table td {{ white-space: normal; }} + .q-table td.break-anywhere {{ overflow-wrap: anywhere; }} """ @@ -63,7 +73,7 @@ def frame( if header is not None: ui.space() header() - with ui.column().classes("w-full max-w-4xl mx-auto p-6 gap-6"): + with ui.column().classes(f"w-full {CONTENT_WIDTH} mx-auto p-6 gap-6"): yield footer() @@ -78,7 +88,9 @@ def footer() -> None: .classes(f"bg-[{BLACK}] text-white px-6 py-4") .mark("footer") ): - with ui.row().classes("w-full max-w-4xl mx-auto items-center gap-x-6 gap-y-2"): + with ui.row().classes( + f"w-full {CONTENT_WIDTH} mx-auto items-center gap-x-6 gap-y-2" + ): for label in SOCIALS: name = label.lower() url = getattr(settings, f"footer_{name}") @@ -100,7 +112,7 @@ def footer() -> None: ui.link( "Privacy policy", settings.footer_privacy_url, new_tab=True ).classes("text-white").mark("footer-privacy") - with ui.column().classes("w-full max-w-4xl mx-auto gap-0 text-caption"): + with ui.column().classes(f"w-full {CONTENT_WIDTH} mx-auto gap-0 text-caption"): if settings.footer_address: ui.label(settings.footer_address) ui.label(f"© {date.today().year} VIVES. All rights reserved.") diff --git a/frontend/src/pmp_frontend/users_page.py b/frontend/src/pmp_frontend/users_page.py index 878e394..0f0ebff 100644 --- a/frontend/src/pmp_frontend/users_page.py +++ b/frontend/src/pmp_frontend/users_page.py @@ -16,6 +16,7 @@ role_page, token, ) +from pmp_frontend.theme import BREAK_ANYWHERE from pmp_frontend.user_import import ( ROLES, YEARS, @@ -37,8 +38,20 @@ "sortable": True, "align": "left", }, - {"name": "email", "label": "Email", "field": "email", "align": "left"}, - {"name": "github", "label": "GitHub", "field": "github", "align": "left"}, + { + "name": "email", + "label": "Email", + "field": "email", + "align": "left", + "classes": BREAK_ANYWHERE, + }, + { + "name": "github", + "label": "GitHub", + "field": "github", + "align": "left", + "classes": BREAK_ANYWHERE, + }, { "name": "role", "label": "Role", diff --git a/frontend/tests/test_navigation.py b/frontend/tests/test_navigation.py index 2532c05..a6f9e62 100644 --- a/frontend/tests/test_navigation.py +++ b/frontend/tests/test_navigation.py @@ -117,3 +117,16 @@ async def test_footer_links_come_from_the_environment( await user.should_see("VIVES Kortrijk · Doorniksesteenweg 145") youtube = user.find(marker="footer-youtube").elements.pop() assert youtube.props["href"] == "https://www.youtube.com/vivesvideo" + + +async def test_tables_fit_the_screen(user: User): + await ready_to_work(user, "superuser") + await user.should_see(marker="users", retries=30) + + # The wide content column, and email addresses that may break (#89). + columns = user.find(kind=ui.column).elements + assert any("max-w-7xl" in column.classes for column in columns) + table = user.find(marker="users").elements.pop() + assert isinstance(table, ui.table) + email = next(c for c in table.columns if c["name"] == "email") + assert email["classes"] == "break-anywhere"