Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 47 additions & 29 deletions frontend/src/pmp_frontend/projects_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,24 @@ async def create() -> None:
ui.button("Create", on_click=create).mark("create-project")
ui.button("Cancel", on_click=dialog.close).props("flat color=dark")

# The one everyday action next to the heading; the GitHub and Trello
# actions for all Projects in a menu each, by the filter (#81).
with ui.row().classes("w-full items-center"):
ui.label("Projects").classes("text-h4")
ui.space()
ui.button("New Project", icon="add", on_click=dialog.open).mark(
"new-project"
)
with ui.row().classes("w-full items-center gap-2"):
# Active Projects are the current work; Archived ones the history.
status_filter = ui.toggle(
{"active": "Active", "archived": "Archived"},
value="active",
on_change=refresh,
).mark("status-filter")
ui.space()
await github_buttons(refresh)
await trello_buttons(refresh)
ui.button("New Project", on_click=dialog.open).mark("new-project")
# Active Projects are the current work; Archived ones the history overview.
status_filter = ui.toggle(
{"active": "Active", "archived": "Archived"},
value="active",
on_change=refresh,
).mark("status-filter")
table = ui.table(columns=COLUMNS, rows=[], row_key="id").classes("w-full")
table.mark("projects")
table.add_slot("body-cell-photo", THUMBNAIL_CELL)
Expand Down Expand Up @@ -236,25 +242,36 @@ async def toggle_choice(event) -> None:
archived = project["status"] == "archived"
ui.link("← Projects", "/projects").classes("text-dark")
heading = ui.label(project["title"]).classes("text-h4")
with ui.row().classes("items-center gap-4"):
ui.label(f"Status: {project['status'].capitalize()}")
ui.button(
"Restore" if archived else "Archive", on_click=change_status
).props("outline color=dark").mark("change-status")
# Only Active Projects without Makers can be deleted (the backend
# also refuses one with Members, with a message).
if not archived and not project["makers"]:
ui.button("Delete", on_click=delete).props(
"outline color=negative"
).mark("delete-project")
if not archived:
# Students can pick it in their Top 3 (spec #37).
ui.switch(
"Open for choice",
value=project["open_for_choice"],
on_change=toggle_choice,
).mark("open-for-choice")
status_error = ErrorMessage()
# Status, Open for choice, Archive/Restore and Delete together in one
# card instead of loose under the title (#81).
with ui.card().classes("w-full max-w-lg").mark("status-card"):
with ui.row().classes("w-full items-center"):
ui.label("Status").classes("text-h6")
ui.badge(
project["status"].capitalize(),
color="grey-7" if archived else "positive",
).mark("status-badge")
if not archived:
# Students can pick it in their Top 3 (spec #37).
ui.switch(
"Open for choice",
value=project["open_for_choice"],
on_change=toggle_choice,
).mark("open-for-choice")
status_error = ErrorMessage()
with ui.row().classes("w-full items-center gap-2"):
ui.button(
"Restore" if archived else "Archive",
icon="unarchive" if archived else "archive",
on_click=change_status,
).props("outline color=dark").mark("change-status")
ui.space()
# Only Active Projects without Makers can be deleted (the
# backend also refuses one with Members, with a message).
if not archived and not project["makers"]:
ui.button("Delete", icon="delete", on_click=delete).props(
"flat color=negative"
).mark("delete-project")
photo_card(project, editable=not archived)
await repository_card(project, editable=not archived)
await trello_card(project, editable=not archived)
Expand Down Expand Up @@ -369,11 +386,12 @@ def members() -> None:
).mark("remove-member")

members()
choice = ui.select({}, label="Add a Student", with_input=True).classes("w-full")
choice.mark("student-choice")
with ui.row().classes("w-full items-center no-wrap"):
choice = ui.select({}, label="Add a Student", with_input=True).classes("grow")
choice.mark("student-choice")
ui.button("Add Member", icon="person_add", on_click=add).mark("add-member")
update_choices()
add_error = ErrorMessage()
ui.button("Add Member", on_click=add).mark("add-member")


def _read_only_details(project: dict[str, Any]) -> None:
Expand Down
17 changes: 9 additions & 8 deletions frontend/src/pmp_frontend/repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ def show_results(title: str, lines: list[tuple[str, bool]]) -> None:


async def github_buttons(after) -> None:
""" "Create repos for all Projects" and "Check all members", or why they are
missing; `after` reloads the Projects table."""
"""A "GitHub" menu with "Create repos for all Projects" and "Check all
members", or why it is missing; `after` reloads the Projects table."""
status = await api.github_status(token())
if not status["connected"]:
ui.label("GitHub isn't connected.").classes("text-caption").mark(
Expand Down Expand Up @@ -164,12 +164,13 @@ async def check_all() -> None:
)
await after()

ui.button("Create repos for all Projects", on_click=create).props(
"outline color=dark"
).mark("create-repos")
ui.button("Check all members", on_click=check_all).props("outline color=dark").mark(
"check-all-members"
)
with (
ui.dropdown_button("GitHub", icon="code")
.props("outline color=dark no-caps")
.mark("github-menu")
):
ui.item("Create repos for all Projects", on_click=create).mark("create-repos")
ui.item("Check all members", on_click=check_all).mark("check-all-members")


NEXT_STEP = {
Expand Down
21 changes: 13 additions & 8 deletions frontend/src/pmp_frontend/trello.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ async def access() -> None:


async def trello_buttons(after) -> None:
""" "Create Trello boards for all Projects" and "Check all Trello members", or
why they are missing; `after` reloads the Projects table."""
"""A "Trello" menu with "Create Trello boards for all Projects" and "Check all
Trello members", or why it is missing; `after` reloads the Projects table."""
status = await api.trello_status(token())
if not status["connected"]:
ui.label("Trello isn't connected.").classes("text-caption").mark(
Expand Down Expand Up @@ -137,12 +137,17 @@ async def check_all() -> None:
)
await after()

ui.button("Create Trello boards for all Projects", on_click=create).props(
"outline color=dark"
).mark("create-trello-boards")
ui.button("Check all Trello members", on_click=check_all).props(
"outline color=dark"
).mark("check-all-trello-members")
with (
ui.dropdown_button("Trello", icon="view_kanban")
.props("outline color=dark no-caps")
.mark("trello-menu")
):
ui.item("Create Trello boards for all Projects", on_click=create).mark(
"create-trello-boards"
)
ui.item("Check all Trello members", on_click=check_all).mark(
"check-all-trello-members"
)


async def my_trello_card() -> None:
Expand Down
10 changes: 8 additions & 2 deletions frontend/tests/test_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
)


def status_badge(user: User) -> str | None:
"""The text of the Status card's badge, once the page shows it."""
found = user.find(marker="status-badge").elements
return next((badge.text for badge in found if isinstance(badge, ui.badge)), None)


async def test_teacher_archives_and_restores_a_project(user: User):
await ready_to_work(user, "teacher")
await create_project(user, "Drone", 1, 3)
Expand All @@ -24,7 +30,7 @@ async def test_teacher_archives_and_restores_a_project(user: User):
user.find(marker="confirm").click()

# Read-only, with the former Member listed as a Maker.
await user.should_see("Status: Archived", retries=30)
await eventually(lambda: status_badge(user) == "Archived")
await user.should_see("Made by")
await user.should_see("Student Account — Electronics-ICT, year 1")
await user.should_not_see(marker="save-project")
Expand All @@ -43,6 +49,6 @@ async def test_teacher_archives_and_restores_a_project(user: User):
await user.should_see("Restore Drone?", retries=10)
user.find(marker="confirm").click()

await user.should_see("Status: Active", retries=30)
await eventually(lambda: status_badge(user) == "Active")
await user.should_see("Members (0 / 1–3)")
await user.should_see("Made by")
Loading