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/bootswatch@5.3.2/dist/flatly/bootstrap.min.css"; | |
| const darkThemeUrl = "https://cdn.jsdelivr.net/npm/bootswatch@5.3.2/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/bootswatch@5.3.2/dist/flatly/bootstrap.min.css"; | |
| const darkThemeUrl = "https://cdn.jsdelivr.net/npm/bootswatch@5.3.2/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) |