File size: 4,863 Bytes
e66cfb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ea68c55
 
 
 
 
 
 
e66cfb4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
student_login.py
----------------
學生登入元件。
用 clientside callback 直接讀 localStorage,避免 Python callback 時序問題。
"""
from __future__ import annotations
import dash
from dash import html, dcc, callback, Input, Output, State, no_update, clientside_callback
import dash_mantine_components as dmc
from dash_iconify import DashIconify


def create_student_store() -> dcc.Store:
    return dcc.Store(id="student-id-store", storage_type="local", data="")


def create_login_modal() -> html.Div:
    return html.Div([
        dmc.Modal(
            id="student-login-modal",
            opened=False,
            centered=True,
            closeOnClickOutside=False,
            closeOnEscape=False,
            withCloseButton=False,
            title=dmc.Group([
                DashIconify(icon="tabler:school", height=24),
                dmc.Text("歡迎使用數學學習平台", fw=700, size="lg"),
            ], gap="xs"),
            children=[
                dmc.Text(
                    "請輸入您的學號,系統將記錄您的學習進度。",
                    size="sm", c="dimmed", mb="md",
                ),
                dmc.TextInput(
                    id="student-id-input",
                    label="學號",
                    placeholder="例如:B11234567",
                    leftSection=DashIconify(icon="tabler:id-badge", height=16),
                    required=True,
                    mb="md",
                ),
                dmc.Alert(
                    id="student-login-error",
                    children="",
                    color="red",
                    variant="light",
                    style={"display": "none"},
                    mb="sm",
                ),
                dmc.Button(
                    "開始學習",
                    id="student-login-btn",
                    fullWidth=True,
                    leftSection=DashIconify(icon="tabler:arrow-right", height=16),
                ),
            ],
            size="sm",
        ),
    ])


def create_student_badge() -> html.Div:
    return html.Div(
        id="student-badge",
        style={"display": "none"},
        children=dmc.Group([
            dmc.Badge(
                id="student-badge-text",
                leftSection=DashIconify(icon="tabler:user", height=12),
                variant="light",
                color="teal",
                size="sm",
            ),
            dmc.ActionIcon(
                DashIconify(icon="tabler:logout", height=14),
                id="student-logout-btn",
                size="sm",
                variant="subtle",
                color="gray",
            ),
        ], gap=4),
    )


@callback(
    Output("student-id-store", "data", allow_duplicate=True),
    Input("student-logout-btn", "n_clicks"),
    prevent_initial_call=True,
)
def logout_student(n_clicks):
    if n_clicks:
        return ""
    return no_update


# ─── Clientside:頁面載入時直接從 localStorage 判斷是否開啟 modal ───────────
# 完全在瀏覽器端執行,不需要等 Python callback,沒有時序問題

clientside_callback(
    """
    function(storeData) {
        try {
            if (storeData && storeData !== "") {
                return false;
            }
            return true;
        } catch(e) {
            return true;
        }
    }
    """,
    Output("student-login-modal", "opened"),
    Input("student-id-store", "data"),
    prevent_initial_call=False,
)


# ─── Python callback:按下「開始學習」後儲存學號 ──────────────────────────

@callback(
    Output("student-id-store", "data"),
    Output("student-login-error", "children"),
    Output("student-login-error", "style"),
    Input("student-login-btn", "n_clicks"),
    State("student-id-input", "value"),
    prevent_initial_call=True,
)
def save_student_id(n_clicks, input_value):
    sid = (input_value or "").strip()
    if not sid:
        return no_update, "請輸入學號", {"display": "block"}

    # 寫入 Firebase
    try:
        from firebase_progress import upsert_student
        upsert_student(sid)
    except Exception as e:
        print(f"[Firebase] upsert_student 失敗:{e}")

    return sid, "", {"display": "none"}


# ─── 學生 badge 更新 ─────────────────────────────────────────────────────

@callback(
    Output("student-badge", "style"),
    Output("student-badge-text", "children"),
    Input("student-id-store", "data"),
)
def update_badge(student_id):
    if student_id:
        return {"display": "inline-flex", "alignItems": "center"}, f"學號:{student_id}"
    return {"display": "none"}, ""