2

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.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.