Spaces:
Sleeping
Sleeping
# app.py (Versi Revisi Siap Deploy) | |
import dash | |
import dash_bootstrap_components as dbc | |
from dash import Dash, dcc, html, Output, Input, State, no_update | |
# Impor dari proyek Anda | |
from pages import ( | |
beranda, | |
analisis_tren_penyakit, | |
distribusi_kasus_demografi, | |
input_data, | |
laporan, | |
pengaturan | |
) | |
from auth import login, signup | |
from components import sidebar | |
# ----------------------------------------------------------------------------- | |
# BAGIAN 1: INISIALISASI APLIKASI (PERUBAHAN UTAMA DI SINI) | |
# ----------------------------------------------------------------------------- | |
# Tidak perlu mengimpor Flask atau menginisialisasi server Flask secara manual lagi. | |
# Dash akan melakukannya secara otomatis. | |
# Konfigurasi Tema | |
THEME_LIGHT = dbc.themes.FLATLY | |
THEME_DARK = dbc.themes.DARKLY | |
# Inisialisasi aplikasi Dash | |
app = Dash( | |
__name__, | |
suppress_callback_exceptions=True, | |
external_stylesheets=[THEME_LIGHT, '/assets/style.css'], | |
meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}] | |
) | |
# Baris ini SANGAT PENTING untuk deployment. | |
# Gunicorn akan mencari variabel bernama 'server'. | |
server = app.server | |
# ----------------------------------------------------------------------------- | |
# BAGIAN 2: LAYOUT UTAMA APLIKASI (TIDAK ADA PERUBAHAN) | |
# ----------------------------------------------------------------------------- | |
app.layout = html.Div([ | |
dcc.Location(id='url', refresh=False), | |
dcc.Store(id='login-status', storage_type='session'), | |
dcc.Store(id='user-theme-preference-store', storage_type='local'), | |
dcc.Store(id='previous-url-store', storage_type='session'), | |
html.Div(id='app-wrapper'), | |
dbc.Modal( | |
[ | |
dbc.ModalHeader(dbc.ModalTitle("Konfirmasi Logout")), | |
dbc.ModalBody("Apakah Anda yakin ingin keluar dari sesi ini?"), | |
dbc.ModalFooter([ | |
dbc.Button("Tidak", id="logout-confirm-no", color="secondary", className="ms-auto", n_clicks=0), | |
dbc.Button("Ya, Logout", id="logout-confirm-yes", color="danger", className="ms-2", n_clicks=0), | |
]), | |
], | |
id="logout-confirm-modal", | |
is_open=False, | |
centered=True, | |
), | |
dcc.Location(id='logout-redirect-location', refresh=True) | |
]) | |
# ----------------------------------------------------------------------------- | |
# BAGIAN 3: CALLBACKS (TIDAK ADA PERUBAHAN) | |
# ----------------------------------------------------------------------------- | |
# --- Callbacks untuk Tema (JavaScript Inline) --- | |
app.clientside_callback( | |
""" | |
function(pathname, storedThemePreference) { | |
let themeToApply = 'LIGHT'; | |
if (storedThemePreference && typeof storedThemePreference.theme === 'string') { | |
themeToApply = storedThemePreference.theme; | |
} | |
const lightThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/flatly/bootstrap.min.css"; | |
const darkThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/darkly/bootstrap.min.css"; | |
let newThemeUrl = lightThemeUrl; | |
document.body.classList.remove('theme-dark', 'theme-light'); | |
if (themeToApply === 'DARK') { | |
newThemeUrl = darkThemeUrl; | |
document.body.classList.add('theme-dark'); | |
} else { | |
newThemeUrl = lightThemeUrl; | |
document.body.classList.add('theme-light'); | |
} | |
let themeLink = document.getElementById('bootstrap-theme'); | |
if (!themeLink) { | |
themeLink = document.createElement('link'); | |
themeLink.id = 'bootstrap-theme'; | |
themeLink.rel = 'stylesheet'; | |
themeLink.type = 'text/css'; | |
document.getElementsByTagName('head')[0].appendChild(themeLink); | |
} | |
if (themeLink.href !== newThemeUrl) { | |
themeLink.href = newThemeUrl; | |
} | |
return null; | |
} | |
""", | |
Output('app-wrapper', 'className'), | |
Input('url', 'pathname'), | |
State('user-theme-preference-store', 'data') | |
) | |
app.clientside_callback( | |
""" | |
function(themePreferenceFromStore) { | |
let themeToApply = 'LIGHT'; | |
if (themePreferenceFromStore && typeof themePreferenceFromStore.theme === 'string') { | |
themeToApply = themePreferenceFromStore.theme; | |
} | |
const lightThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/flatly/bootstrap.min.css"; | |
const darkThemeUrl = "https://cdn.jsdelivr.net/npm/[email protected]/dist/darkly/bootstrap.min.css"; | |
let newThemeUrl = lightThemeUrl; | |
document.body.classList.remove('theme-dark', 'theme-light'); | |
if (themeToApply === 'DARK') { | |
newThemeUrl = darkThemeUrl; | |
document.body.classList.add('theme-dark'); | |
} else { | |
newThemeUrl = lightThemeUrl; | |
document.body.classList.add('theme-light'); | |
} | |
let themeLink = document.getElementById('bootstrap-theme'); | |
if (!themeLink) { | |
themeLink = document.createElement('link'); | |
themeLink.id = 'bootstrap-theme'; | |
themeLink.rel = 'stylesheet'; | |
themeLink.type = 'text/css'; | |
document.getElementsByTagName('head')[0].appendChild(themeLink); | |
} | |
if (themeLink.href !== newThemeUrl) { | |
themeLink.href = newThemeUrl; | |
} | |
return null; | |
} | |
""", | |
Output('app-wrapper', 'className', allow_duplicate=True), | |
Input('user-theme-preference-store', 'data'), | |
prevent_initial_call=True | |
) | |
# Callback untuk Navigasi Halaman, Kontrol Sidebar, dan Modal Logout | |
def display_page_logic(pathname, login_data): | |
is_logged_in = login_data and login_data.get('logged_in', False) | |
no_sidebar_pages = ['/login', '/signup'] | |
open_logout_modal = False | |
if pathname == '/logout' and is_logged_in: | |
open_logout_modal = True | |
# KASUS 1: Pengguna belum login | |
if not is_logged_in: | |
if pathname in no_sidebar_pages or pathname == '/' or pathname is None: | |
if pathname == '/signup': | |
return signup.layout, False | |
else: | |
return login.layout, False | |
else: | |
return dcc.Location(pathname="/login", id="redirect-to-login-unauth"), False | |
# KASUS 2: Pengguna sudah login | |
else: | |
if pathname == '/logout': | |
page_layout_content = beranda.layout | |
elif pathname in no_sidebar_pages or pathname == '/' or pathname is None: | |
return dcc.Location(pathname="/beranda", id="redirect-to-home-auth"), False | |
elif pathname == '/beranda': | |
page_layout_content = beranda.layout | |
elif pathname == '/analisis_tren_penyakit': | |
page_layout_content = analisis_tren_penyakit.layout | |
elif pathname == '/distribusi_kasus_demografi': | |
page_layout_content = distribusi_kasus_demografi.layout | |
elif pathname == '/input_data': | |
page_layout_content = input_data.layout | |
elif pathname == '/laporan': | |
page_layout_content = laporan.layout | |
elif pathname == '/pengaturan': | |
page_layout_content = pengaturan.layout | |
else: | |
page_layout_content = html.H1("404 - Halaman Tidak Ditemukan", className="text-center mt-5") | |
return html.Div([ | |
sidebar.sidebar_layout, | |
html.Div(page_layout_content, id="page-content") | |
]), open_logout_modal | |
# Callback untuk menyimpan URL sebelum logout | |
def store_previous_url(current_pathname, last_stored_url): | |
excluded_paths = ['/logout', '/login', '/signup'] | |
if current_pathname not in excluded_paths and current_pathname != last_stored_url: | |
return current_pathname | |
return no_update | |
# Callback untuk Update Profil di Sidebar | |
def update_sidebar_profile(login_data, pathname): | |
no_sidebar_pages_or_logout = ['/login', '/signup', '/logout'] | |
if login_data and login_data.get('logged_in'): | |
nama_pengguna = login_data.get('nama_lengkap', login_data.get('username', 'Pengguna')) | |
if pathname not in no_sidebar_pages_or_logout: | |
return nama_pengguna, {'display': 'block'} | |
else: | |
return no_update, {'display': 'none'} | |
return "Nama Pengguna", {'display': 'none'} | |
# Callback untuk Aksi Modal Logout | |
def handle_logout_confirmation_app(n_yes, n_no, previous_url): | |
triggered_id = dash.callback_context.triggered_id if dash.callback_context.triggered_id else None | |
if triggered_id == 'logout-confirm-yes': | |
return '/login', True, False | |
elif triggered_id == 'logout-confirm-no': | |
url_to_return = previous_url if previous_url else '/beranda' | |
return url_to_return, False, False | |
return no_update, no_update, False | |
# ----------------------------------------------------------------------------- | |
# BAGIAN 4: MENJALANKAN APLIKASI (TIDAK ADA PERUBAHAN) | |
# ----------------------------------------------------------------------------- | |
# Blok ini memastikan server development hanya berjalan saat file ini dieksekusi langsung, | |
# dan tidak akan berjalan saat diimpor oleh Gunicorn di server produksi. | |
if __name__ == '__main__': | |
app.run(debug=True, port=8050) |