I'm trying to make a Dashboard with Login, Register and Dashboard with Navbar and Tabs. With below code everything working well but I have a problem that if I refreshed page, Dashboard will return to login screen.
from dash import Dash, dcc, html, Input, Output, State, ctx
import dash_mantine_components as dmc
import pandas as pd
import dash
from dash_ag_grid import AgGrid
import plotly.express as px
import os
from datetime import datetime
from pandas.tseries.offsets import BDay
from datetime import datetime, date, timedelta
import base64
import io
import plotly.graph_objects as go
import dash_ag_grid as dag
import numpy as np
excel_file = 'users2.xlsx'
# Tạo file nếu chưa có
if not os.path.exists(excel_file):
pd.DataFrame(columns=["username", "password", "email", "code", "branch", "created_at"]).to_excel(excel_file, index=False)
app = dash.Dash(__name__, suppress_callback_exceptions=True)
def t(lang, key):
translations = {
"vi": {
"login": "Đăng nhập",
"register": "Đăng ký",
"username": "Tên đăng nhập",
"password": "Mật khẩu",
"confirm": "Nhập lại mật khẩu",
"email": "Email",
"code": "Mã nhân viên",
"branch": "Chi nhánh",
"no_account": "Chưa có tài khoản?",
"register_now": "Đăng ký ngay",
"has_account": "Đã có tài khoản?",
"create_account": "Tạo tài khoản",
"success": "Đăng ký thành công. Vui lòng đăng nhập.",
"income": "Thu nhập"
},
"en": {
"login": "Login",
"register": "Register",
"username": "Username",
"password": "Password",
"confirm": "Confirm Password",
"email": "Email",
"code": "Employee Code",
"branch": "Branch",
"no_account": "No account?",
"register_now": "Register now",
"has_account": "Already have an account?",
"create_account": "Create Account",
"success": "Successfully registered. Please login.",
"income": "income"
},
"ko": {
"login": "로그인",
"register": "회원가입",
"username": "사용자 이름",
"password": "비밀번호",
"confirm": "비밀번호 확인",
"email": "이메일",
"code": "사원 번호",
"branch": "지점",
"no_account": "계정이 없으신가요?",
"register_now": "지금 등록하세요",
"has_account": "이미 계정이 있으신가요?",
"create_account": "계정 만들기",
"success": "성공적으로 등록되었습니다. 로그인하세요.",
"income": "수익성"
}
}
return translations.get(lang, translations["vi"]).get(key, key)
def login_screen(lang):
return dmc.Container([
dmc.Select(
id="language-select",
label="Ngôn ngữ / Language / 언어",
data=[
{"label": "Tiếng Việt", "value": "vi"},
{"label": "English", "value": "en"},
{"label": "한국어", "value": "ko"}
],
value=lang,
style={"marginBottom": 20}
),
dmc.Title(t(lang, "login"), order=2, align="center", mb=20),
dmc.TextInput(id="login-username", label=t(lang, "username")),
dmc.PasswordInput(id="login-password", label=t(lang, "password"), mt=10),
dmc.Button(t(lang, "login"), id="login-submit", mt=20),
dmc.Text(id="alert-text", color="red", size="sm", mt=10),
dmc.Text(t(lang, "no_account"), span=True),
dmc.Button(t(lang, "register_now"), id="go-register", variant="subtle", compact=True)
], style={"maxWidth": 400, "margin": "auto", "paddingTop": 100})
def register_screen(lang):
return dmc.Container([
dmc.Title(t(lang, "register"), order=2, align="center", mb=20),
dmc.TextInput(id="register-username", label=t(lang, "username")),
dmc.PasswordInput(id="register-password", label=t(lang, "password"), mt=10),
dmc.PasswordInput(id="register-confirm", label=t(lang, "confirm"), mt=10),
dmc.TextInput(id="register-email", label=t(lang, "email"), mt=10),
dmc.TextInput(id="register-code", label=t(lang, "code"), mt=10),
dmc.TextInput(id="register-branch", label=t(lang, "branch"), mt=10),
dmc.Button(t(lang, "create_account"), id="register-submit", fullWidth=True, mt=20),
dmc.Text(id="alert-text", color="red", size="sm", mt=10),
dmc.Text(t(lang, "has_account"), span=True),
dmc.Button(t(lang, "login"), id="go-login", variant="subtle", compact=True)
], style={"maxWidth": 400, "margin": "auto", "paddingTop": 100})
def full_dashboard_layout(lang):
return dmc.Container([
dmc.Group([
dmc.Switch(id="theme-toggle", label="Dark mode", size="md", offLabel="☀️", onLabel="🌙"),
dmc.Button("Signout", id="sign-out", variant="light", color="red", size="sm")
], position="apart", mb=20),
html.Div([
html.Div(id="sidebar", children=[
dmc.Stack([
dmc.NavLink(label=t(lang, "income"), id="nav-1-btn"),
dmc.NavLink(label="Nav 2", id="nav-2-btn"),
dmc.NavLink(label="Nav 3", id="nav-3-btn"),
])
], style={
"width": "150px",
"minHeight": "100vh",
"borderRight": "1px solid #eee",
"padding": "10px"
}),
html.Div(id="content-area", style={"flex": 1, "padding": "20px"})
], style={"display": "flex"})
], fluid = True)
app.layout = dmc.Container([
dcc.Location(id="url", refresh=False),
dcc.Store(id="auth-status", data="login", storage_type = "local"),
dcc.Store(id="selected-lang", data="vi"),
dcc.Store(id="alert-store", data={}),
dcc.Store(id="theme-store", data="light"),
dmc.MantineProvider(
id="theme-provider",
theme={"colorScheme": "light"},
withGlobalStyles=True,
withNormalizeCSS=True,
children=html.Div(id="app-screen")
),
dmc.Text(id="alert-text", color="red", size="sm", mt=10)
], fluid = True)
@app.callback(Output("app-screen", "children"),
Input("auth-status", "data"),
State("selected-lang", "data"))
def render_app(status, lang):
if status == "login": return login_screen(lang)
if status == "register": return register_screen(lang)
if status == "authenticated": return full_dashboard_layout(lang)
@app.callback(
Output("auth-status", "data", allow_duplicate=True),
Input("go-login", "n_clicks"), prevent_initial_call=True, prevent_initial_callbacks=True)
def back_to_login(go_login): return "login"
@app.callback(
Output("auth-status", "data"),
Output("alert-store", "data", allow_duplicate=True),
Input("go-register", "n_clicks"),
Input("login-submit", "n_clicks"),
State("login-username", "value"),
State("login-password", "value"),
prevent_initial_call=True
)
def login_flow(go_reg, login, username, password):
if ctx.triggered_id == "go-register":
return "register", {}
if not username or not password:
return dash.no_update, {}
path = "users2.xlsx"
try:
df = pd.read_excel(path)
if "username" not in df.columns or "password" not in df.columns:
return dash.no_update, {"message": "Invalid data"}
except:
return dash.no_update, {"message": "Cannot read users"}
match = df[(df["username"] == username) & (df["password"] == password)]
if match.empty:
return dash.no_update, {"message": "Sai tên đăng nhập hoặc mật khẩu"}
return "authenticated", {}
@app.callback(
Output("auth-status", "data", allow_duplicate=True),
Output("alert-store", "data", allow_duplicate=True),
Input("register-submit", "n_clicks"),
State("register-username", "value"),
State("register-password", "value"),
State("register-confirm", "value"),
State("register-email", "value"),
State("register-code", "value"),
State("register-branch", "value"),
prevent_initial_call=True
)
def handle_register(_, username, password, confirm, email, code, branch):
if not any([username, password, confirm, email, code, branch]):
return dash.no_update, {}
if not all([username, password, confirm, email, code, branch]):
return dash.no_update, {"message": "Vui lòng nhập đủ thông tin"}
if password != confirm:
return dash.no_update, {"message": "Mật khẩu xác nhận không khớp"}
if not (email.endswith("@wooribank.com") or email.endswith("@woori.com.vn")):
return dash.no_update, {"message": "Email không hợp lệ"}
path = "users2.xlsx"
try:
df = pd.read_excel(path)
except:
df = pd.DataFrame(columns=["username", "password", "email", "code", "branch", "created_at"])
if any(df["username"] == username) or any(df["email"] == email) or any(df["code"] == code):
return dash.no_update, {"message": "Tài khoản/email/mã đã tồn tại"}
df = pd.concat([df, pd.DataFrame([{
"username": username,
"password": password,
"email": email,
"code": code,
"branch": branch,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}])], ignore_index=True)
df.to_excel(path, index=False)
return "register", {"message": "Đăng ký thành công"}
@app.callback(Output("alert-text", "children"),
Input("alert-store", "data"))
def show_alert(data):
return data.get("message", "") if isinstance(data, dict) else ""
@app.callback(Output("alert-text", "color"),
Input("auth-status", "data"),
Input("alert-store", "data"))
def alert_color(status, data):
if status == "register" and data.get("message") == "Đăng ký thành công":
return "green"
return "red"
@app.callback(
Output("app-screen", "children", allow_duplicate=True),
Input("selected-lang", "data"),
State("auth-status", "data"),
prevent_initial_call=True
)
def update_lang_change(lang, status):
if status == "login":
return login_screen(lang)
elif status == "register":
return register_screen(lang)
return dash.no_update
@app.callback(Output("theme-store", "data"),
Input("theme-toggle", "checked"), prevent_initial_call=True)
def toggle_theme(checked): return "dark" if checked else "light"
@app.callback(Output("theme-provider", "theme"),
Input("theme-store", "data"))
def update_theme(scheme): return {"colorScheme": scheme}
@app.callback(Output("selected-lang", "data"),
Input("language-select", "value"))
def set_language(value): return value
@app.callback(
Output("content-area", "children"),
Input("nav-1-btn", "n_clicks"),
Input("nav-2-btn", "n_clicks"),
Input("nav-3-btn", "n_clicks"),
Input("auth-status", "data"),
State("theme-store", "data"),
State("selected-lang", "data")
)
def update_content(n1, n2, n3, auth_status, theme, lang):
triggered = ctx.triggered_id
if triggered in ["nav-1-btn","nav-2-btn","nav-3-btn"]:
return generate_tabs(triggered.replace("-btn",""), theme, lang)
if auth_status:
return generate_tabs("nav-1", theme, lang)
return dmc.Text(t(lang, "choose_menu"))
@app.callback(
Output("auth-status", "data", allow_duplicate=True),
Input("sign-out", "n_clicks"),
prevent_initial_call=True,
prevent_initial_callbacks=True
)
def logout(n):
return "login"
def generate_tabs(nav_id, theme, lang):
if nav_id == "nav-1":
return dmc.Tabs(
id="nav-1-tabs",
value="tab-1",
children=[
dmc.TabsList([
dmc.Tab(t(lang, "tab1"), value="tab-1"),
dmc.Tab(t(lang, "tab2"), value="tab-2"),
dmc.Tab(t(lang, "tab3"), value="tab-3"),
]),
dmc.TabsPanel(id="nav-1-tab-content", value="tab-1"),
dmc.TabsPanel(dmc.Text(t(lang, "tab2_content")), value="tab-2"),
dmc.TabsPanel(dmc.Text(t(lang, "tab3_content")), value="tab-3"),
]
)
elif nav_id == "nav-2":
return dmc.Tabs(
id="nav-2-tabs",
value="tab-a",
children=[
dmc.TabsList([
dmc.Tab("Bộ lọc", value="tab-a"),
dmc.Tab("Lịch", value="tab-b"),
]),
dmc.TabsPanel([
dmc.Select(label="Chi nhánh", data=["Hà Nội", "HCM"]),
dmc.DatePicker()
], value="tab-a"),
dmc.TabsPanel(dmc.Text("Lịch hoạt động ở đây..."), value="tab-b"),
]
)
elif nav_id == "nav-3":
return dmc.Tabs(
id="nav-3-tabs",
value="tab-x",
children=[
dmc.TabsList([
dmc.Tab("Thông báo", value="tab-x"),
dmc.Tab("Người dùng", value="tab-y"),
dmc.Tab("Vai trò", value="tab-z"),
dmc.Tab("Báo cáo", value="tab-w")
]),
dmc.TabsPanel(dmc.Text("Thông báo hệ thống"), value="tab-x"),
dmc.TabsPanel(dmc.Text("Danh sách người dùng"), value="tab-y"),
dmc.TabsPanel(dmc.Text("Quản lý vai trò"), value="tab-z"),
dmc.TabsPanel(dmc.Text("Báo cáo tổng hợp"), value="tab-w"),
]
)
return dmc.Text("Không có nội dung.")
def blank_fig(is_dark):
#fig = go.Figure(go.Bar(x=[], y=[]))
layout = go.Layout(
paper_bgcolor= "#1A1B1E" if is_dark else "#ffffff",
plot_bgcolor= "#1A1B1E" if is_dark else "#ffffff",
font=dict(color="#ffffff" if is_dark else "#000000"),
xaxis=dict(visible=True),
yaxis=dict(visible=True),
annotations=[
dict(
text="Không có dữ liệu",
x='0.5', y='0.5',
font=dict(size=20, color = "#ffffff" if is_dark else "#000000"),
showarrow = False
)
]
)
return go.Figure(data=[], layout=layout)
def blank_fig2(is_dark):
fig2 = go.Figure(go.Bar(x=[], y=[]))
fig2.update_layout({'paper_bgcolor':'rgba(0,0,0,0)','plot_bgcolor':'rgba(0,0,0,0)'},
margin = dict(l=10,r=10,t=10, b=10),font_color='#ffffff' if is_dark else "#000000", height=200)
fig2.update_xaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True)
fig2.update_yaxes(showline=False,showgrid=False,exponentformat="none",separatethousands=True)
return fig2
def get_dates(start_date, num_days=365):
return [(start_date + timedelta(days=i)).isoformat()
for i in range(num_days)
if (start_date + timedelta(days=i)).weekday()==4 or (start_date + timedelta(days=i)).weekday()==5]
@app.callback(
Output("nav-1-tab-content", "children"),
Input("nav-1-tabs", "value"),
Input("theme-store", "data"),
State("selected-lang", "data")
)
def nav1_tabs(tab, theme, lang):
is_dark = theme == "dark"
if tab == "tab-1":
return dmc.LoadingOverlay([
dmc.Container([
dmc.Grid([
dmc.Col([
dmc.DatePicker(id='datepickersingle_1',
value=[],
minDate=date(2023,12,29),
disabledDates = get_dates(date.today()),
style={'width': 200})
], span='auto'),
dmc.Col([
dmc.DatePicker(id='datepickersingle_2',
value=[],
minDate=date(2023,12,29),
disabledDates = get_dates(date.today()),
style={'width': 200})
], span='auto'),
dmc.Col([
dmc.DatePicker(id='datepickersingle_3',
value=[],
minDate=date(2023,12,29),
disabledDates = get_dates(date.today()),
style={'width': 200})
], span='auto'),
dmc.Col([
dmc.DatePicker(id='datepickersingle_4',
value=(datetime.today() - BDay(1)).date(),
minDate=date(2023,12,29),
disabledDates = get_dates(date.today()),
style={'width': 200})
], span='auto'),
dmc.Col([
dmc.NumberInput(id='input_1',
label='KPI Exchange Rate',
value=[],
precision=4,
style={'width': 200})
], span='auto'),
dmc.Col([
dmc.Select(label = "Select branch",
id='dropdown_1',
data = ['001+','100','101','102','103','200','201','202','300','301',
'302','303','304','500','700','701','702','703', '203'],
placeholder = 'Please select branch',
value = '001+', clearable=True)
], span='auto'),
dmc.Col([
dmc.Button('Submit', color='gray', id='btn_1')
], span='auto')
], align='flex-start', justify = 'center', style={'margin-top':10}),
], fluid=True)
], loaderProps={'variant':"dot", 'color':'blue', 'fullscreen':True})
return dash.no_update
if __name__ == "__main__":
app.run_server(debug=False, port = 1522, jupyter_mode="external")
I'm guessing that login and Dashboard has same link and not be separated with href so this situation but I'm not sure how to fix it.