Skip to content

Commit ce177f0

Browse files
committed
Align settings with Figma design
1 parent d786196 commit ce177f0

8 files changed

Lines changed: 634 additions & 340 deletions

File tree

app.py

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@
101101
friendly_connection_error,
102102
)
103103
from office_app.ui.views import StudentListView
104-
from office_app.ui.views.settings_view import SettingsView
104+
from office_app.ui.views.settings_figma_view import SettingsView
105105
from office_app.services.updater_service import UpdaterService
106106

107107
def app_version_label() -> str:
@@ -1410,6 +1410,12 @@ def __init__(self, sb: Client, initial_user: str = "Joshua"):
14101410
self.settings_view.connection_settings_requested.connect(
14111411
self._open_connection_settings
14121412
)
1413+
self.settings_view.test_connection_requested.connect(
1414+
self._test_settings_connection
1415+
)
1416+
self.settings_view.sync_now_requested.connect(
1417+
self.sync_google_sheet_now
1418+
)
14131419
self.settings_view.preferences_changed.connect(
14141420
self._apply_accessibility_preferences
14151421
)
@@ -1446,6 +1452,9 @@ def _set_connection_state(self, text: str, state: str) -> None:
14461452
badge = getattr(self, "connection_badge", None)
14471453
if badge is not None:
14481454
badge.set_state(state, text)
1455+
settings_view = getattr(self, "settings_view", None)
1456+
if settings_view is not None:
1457+
settings_view.set_connection_state(text, state)
14491458

14501459
def _refresh_database_updated_label(self):
14511460
if self._last_database_update_at is None:
@@ -1513,7 +1522,10 @@ def _update_page_header(self, index: int) -> None:
15131522
4: ("Expenses", "Budget and expense history for the selected student"),
15141523
5: ("Workbook", "Review and safely update the local master workbook"),
15151524
6: ("Coordinators", "Contact directory for ministry coordinators"),
1516-
7: ("Settings", "Local workbook and appearance preferences"),
1525+
7: (
1526+
"Settings",
1527+
"Configure appearance, accessibility, and office connections.",
1528+
),
15171529
}
15181530
title, subtitle = pages.get(index, ("SSM Workspace", "Student support records"))
15191531
self.page_title_label.setText(title)
@@ -1600,6 +1612,8 @@ def _update_compact_header(self) -> None:
16001612
self._update_page_header(self.stacked_widget.currentIndex())
16011613
if hasattr(self, "sync_panel"):
16021614
self.sync_panel.setFixedHeight(126 if compact else 96)
1615+
if hasattr(self, "settings_view"):
1616+
self.settings_view.set_compact(compact)
16031617
self.page_eyebrow_label.hide()
16041618

16051619
def _dashboard_greeting(self) -> str:
@@ -2094,6 +2108,9 @@ def nav_coordinators(self):
20942108
def nav_settings(self):
20952109
self._set_active_nav(self.btn_settings)
20962110
self._switch_page(7)
2111+
self.settings_view.load_settings()
2112+
self.refresh_sync_status()
2113+
self._test_settings_connection(silent=True)
20972114

20982115
def _on_user_changed(self, name):
20992116
if not name:
@@ -2132,6 +2149,28 @@ def _open_connection_settings(self) -> None:
21322149
),
21332150
)
21342151

2152+
def _test_settings_connection(self, *_args, silent=False) -> None:
2153+
"""Test the live repository without blocking the Settings screen."""
2154+
self.settings_view.set_connection_state("Checking", "loading")
2155+
2156+
def connected(_rows):
2157+
self._mark_database_updated()
2158+
if not silent:
2159+
self.status_bar.showMessage("Database connection is working.", 4000)
2160+
2161+
def failed(error):
2162+
self._set_connection_state("Connection issue", "danger")
2163+
self.status_bar.showMessage(
2164+
"Could not reach the office database. Check the saved connection.",
2165+
7000,
2166+
)
2167+
logging.getLogger(__name__).warning(
2168+
"Settings connection test failed: %s",
2169+
str(error).strip().splitlines()[-1],
2170+
)
2171+
2172+
self._run_background(self.student_repository.ping, connected, failed)
2173+
21352174
def _audit(self, action, entity_type, entity_id=None, details=None):
21362175
operator = getattr(self, "_current_operator", self._initial_user)
21372176
self._run_background(
@@ -2658,6 +2697,11 @@ def apply_status(entry):
26582697
)
26592698
self.sync_last_label.setText("No successful sync recorded")
26602699
self.sync_records_label.setText("—")
2700+
self.settings_view.set_sync_status(
2701+
configured=has_token,
2702+
state="Ready to sync" if has_token else "Token needed",
2703+
last_sync="No successful sync recorded",
2704+
)
26612705
return
26622706
details = entry.get("details") or {}
26632707
self.sync_state_badge.set_state(
@@ -2672,6 +2716,19 @@ def apply_status(entry):
26722716
self.sync_records_label.setText(
26732717
self._format_sync_counts(details)
26742718
)
2719+
self.settings_view.set_sync_status(
2720+
configured=has_token,
2721+
state="Ready to sync" if has_token else "Token needed",
2722+
last_sync=self.sheet_sync_service.format_timestamp(
2723+
entry.get("created_at")
2724+
),
2725+
active_rows=int(details.get("students") or 0),
2726+
source=str(
2727+
details.get("source")
2728+
or details.get("source_sheet_name")
2729+
or "SSM Masterlist / Current workbook"
2730+
),
2731+
)
26752732

26762733
def failed(error):
26772734
self._sync_pulse.stop()
@@ -2681,6 +2738,11 @@ def failed(error):
26812738
self.sync_state_badge.set_state("warning", "Status unavailable")
26822739
self.sync_last_label.setText("Could not read the audit log")
26832740
self.sync_records_label.setText("—")
2741+
self.settings_view.set_sync_status(
2742+
configured=bool(get_sheet_sync_token()),
2743+
state="Status unavailable",
2744+
last_sync="Could not read the audit log",
2745+
)
26842746

26852747
return self._run_background(
26862748
self.sheet_sync_service.latest_success,
@@ -2697,7 +2759,7 @@ def sync_google_sheet_now(self):
26972759
"Add the private Google Sheet sync token in Settings.", 6000
26982760
)
26992761
self.nav_settings()
2700-
self.settings_view.sync_token_input.setFocus()
2762+
QTimer.singleShot(0, self.settings_view.prompt_replace_token)
27012763
return None
27022764

27032765
decision = QMessageBox.question(
@@ -2752,6 +2814,7 @@ def synchronized(result):
27522814
self.student_list_view.load_student_list()
27532815
if self.stacked_widget.currentIndex() == 6:
27542816
self.load_coordinators()
2817+
self.refresh_sync_status()
27552818

27562819
def failed(error):
27572820
self._set_sync_busy(False)
@@ -2768,6 +2831,7 @@ def _set_sync_busy(self, busy: bool) -> None:
27682831
self.sync_now_button.setEnabled(not busy)
27692832
self.sync_now_button.setText("Syncing…" if busy else "Sync now")
27702833
self.refresh_data_button.setEnabled(not busy)
2834+
self.settings_view.set_sync_busy(busy)
27712835

27722836
@staticmethod
27732837
def _format_sync_counts(details) -> str:
@@ -3565,7 +3629,7 @@ def create_profile_screen(self):
35653629

35663630
self.photo_label = QLabel("Photo")
35673631
self.photo_label.setObjectName("ProfileAvatar")
3568-
self.photo_label.setFixedSize(72, 72)
3632+
self.photo_label.setFixedSize(96, 96)
35693633
self.photo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
35703634
summary_layout.addWidget(self.photo_label)
35713635

assets/styles/app.qss

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,9 @@ QFrame#ProfileAccent[status="graduated"] { background: @graduated; }
258258
*#ProfileAvatar {
259259
background: @graduated_soft;
260260
border: none;
261-
border-radius: 36px;
261+
border-radius: 48px;
262262
color: @graduated;
263-
font-size: 12px;
263+
font-size: 14px;
264264
font-weight: 700;
265265
}
266266
QWidget#ProfileInfoCard {
@@ -410,16 +410,55 @@ QWidget#ExpenseContextBar {
410410
}
411411
*#ExpenseStudentName { color: @text_primary; font-size: 13px; font-weight: 700; }
412412
QWidget#SettingsSectionRow { background: transparent; border: none; }
413-
QWidget#SettingsToggleRow {
414-
background: @surface_subtle;
415-
border: 1px solid @border_subtle;
416-
border-radius: 8px;
413+
QFrame#SettingsPreferenceRow {
414+
background: transparent;
415+
border: none;
416+
border-bottom: 1px solid @border_subtle;
417+
border-radius: 0;
418+
}
419+
*#SettingsPreferenceTitle {
420+
color: @text_primary;
421+
font-size: 13px;
422+
font-weight: 700;
423+
}
424+
*#SettingsPreferenceCopy {
425+
color: @text_secondary;
426+
font-size: 11px;
417427
}
418-
QWidget#DatabaseStatePanel {
428+
*#SettingsProjectValue {
429+
color: @text_primary;
430+
font-size: 12px;
431+
font-weight: 650;
432+
}
433+
QFrame#SettingsTokenPanel {
419434
background: @surface_subtle;
420435
border: 1px solid @border_subtle;
421436
border-radius: 8px;
422437
}
438+
QFrame#SettingsSyncAccent {
439+
background: @accent;
440+
border: none;
441+
border-radius: 2px;
442+
}
443+
*#SettingsMetaLabel {
444+
color: @text_secondary;
445+
font-size: 10px;
446+
font-weight: 650;
447+
}
448+
*#SettingsMetaValue {
449+
color: @text_primary;
450+
font-size: 12px;
451+
font-weight: 650;
452+
}
453+
*#SettingsSyncNotice {
454+
padding: 0 16px;
455+
background: @warning_soft;
456+
border: none;
457+
border-radius: 7px;
458+
color: @warning;
459+
font-size: 11px;
460+
font-weight: 550;
461+
}
423462
QWidget#SyncPanel {
424463
background: transparent;
425464
border: none;
@@ -433,6 +472,15 @@ QFrame#SyncAccentRail { background: @accent; border: none; border-radius: 2px; }
433472
*#SyncStateBadge[state="success"], *#SyncTokenBadge[state="success"] {
434473
background: @success_soft; border-color: @success; color: @success;
435474
}
475+
*#SettingsDatabaseBadge[state="success"] {
476+
background: @success_soft; border-color: @success; color: @success;
477+
}
478+
*#SettingsDatabaseBadge[state="loading"] {
479+
background: @accent_soft; border-color: @accent; color: @warning;
480+
}
481+
*#SettingsDatabaseBadge[state="danger"] {
482+
background: @danger_soft; border-color: @danger; color: @danger;
483+
}
436484
*#SyncStateBadge[state="warning"], *#SyncTokenBadge[state="warning"] {
437485
background: @warning_soft; border-color: @warning; color: @warning;
438486
}

office_app/services/student_list_service.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class StudentListService:
1111
"""Business logic for student list filters, area counts, and grade options."""
1212

1313
ALL_GRADES = "All years"
14+
SORT_LAST_NAME = "last_name"
15+
SORT_SPONSOR = "sponsor"
1416

1517
def __init__(self, student_service: StudentService | None = None) -> None:
1618
self.student_service = student_service or StudentService()
@@ -60,3 +62,9 @@ def grade_options(self, rows: Sequence[Mapping[str, Any]]) -> List[str]:
6062
grades_by_key.values(),
6163
key=self.student_service.grade_sort_key,
6264
)
65+
66+
def sort_order(self, sort_key: str) -> List[str]:
67+
"""Return a stable repository order for the selected directory sort."""
68+
if sort_key == self.SORT_SPONSOR:
69+
return ["sponsor", "last_name", "first_name", "id"]
70+
return ["last_name", "first_name", "id"]

office_app/services/updater_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class UpdaterService:
99
# Change this whenever you compile a new .exe!
10-
CURRENT_VERSION = "1.0.20"
10+
CURRENT_VERSION = "1.0.21"
1111
MIN_INSTALLER_BYTES = 40 * 1024 * 1024
1212

1313
def __init__(self, client):

0 commit comments

Comments
 (0)