revisi_skripsi / auth /signup.py
aslasacacc's picture
The real final commit, clean start
4253e50
# auth/signup.py
from dash import html, dcc, Input, Output, State, callback, no_update
import dash_bootstrap_components as dbc
from database import engine, users
from sqlalchemy import select, insert
from werkzeug.security import generate_password_hash
# Opsi untuk Jabatan
role_options = [
{'label': 'Petugas Kesehatan Dasar', 'value': 'petugas_kesdas'},
{'label': 'Petugas Rekam Medis Puskesmas', 'value': 'pmik'},
{'label': 'Peneliti', 'value': 'peneliti'},
{'label': 'Lainnya', 'value': 'lainnya'}
]
signup_layout = dbc.Container(
fluid=True, # Mengambil lebar penuh viewport
className="d-flex flex-column justify-content-center align-items-center min-vh-100 p-0 m-0", # Center vertikal & horizontal, tinggi minimal 100% viewport, hapus padding/margin default container
style={'background': 'linear-gradient(135deg, #1e2a47, #2c3e50)'},
children=[
dcc.Location(id='signup-url-redirect', refresh=True),
dbc.Row( # Baris untuk menengahkan Card
dbc.Col( # Kolom untuk Card, batasi lebar
dbc.Card(
dbc.CardBody([
html.H2("Daftar Akun Baru", className="text-center mb-4", style={'color': '#2c3e50'}),
# Pesan error/sukses di atas form
html.Div(id='signup-message', className="mb-3 text-center"),
dbc.Form([
# Nama Lengkap
dbc.Row([
dbc.Label("Nama Lengkap", width=4, className="text-md-end"), # Label di kiri pada layar medium+
dbc.Col(
dbc.Input(id='signup-fullname', type='text', placeholder='Masukkan nama lengkap Anda'),
width=8
)
], className="mb-3 align-items-center"),
# Username
dbc.Row([
dbc.Label("Username", width=4, className="text-md-end"),
dbc.Col(
dbc.Input(id='signup-username', type='text', placeholder='Pilih username (unik)'),
width=8
)
], className="mb-3 align-items-center"),
# Email
dbc.Row([
dbc.Label("Email (Opsional)", width=4, className="text-md-end"),
dbc.Col(
dbc.Input(id='signup-email', type='email', placeholder='Masukkan alamat email'),
width=8
)
], className="mb-3 align-items-center"),
# Password
dbc.Row([
dbc.Label("Password", width=4, className="text-md-end"),
dbc.Col(
dbc.Input(id='signup-password', type='password', placeholder='Buat password (minimal 6 karakter)'),
width=8
)
], className="mb-3 align-items-center"),
# Konfirmasi Password
dbc.Row([
dbc.Label("Konfirmasi Password", width=4, className="text-md-end"),
dbc.Col(
dbc.Input(id='signup-confirm-password', type='password', placeholder='Ulangi password'),
width=8
)
], className="mb-3 align-items-center"),
# Jabatan
dbc.Row([
dbc.Label("Jabatan", width=4, className="text-md-end"),
dbc.Col(
dcc.Dropdown(
id='signup-role',
options=role_options,
placeholder='Pilih jabatan Anda',
),
width=8
)
], className="mb-3 align-items-center"),
dbc.Button("Daftar", id='signup-button', color="success", className="w-100 mt-4", n_clicks=0, size="lg"),
]),
html.Div(
dcc.Link("Sudah punya akun? Login di sini", href="/login", className="d-block mt-3 text-center"),
)
]),
className="shadow-lg", # Shadow pada card
style={'padding': '2rem'} # Tambahkan padding dalam card
),
width=12, # Lebar kolom untuk layar kecil
sm=10, # Sedikit lebih sempit di layar small
md=8, # Lebih sempit lagi di layar medium
lg=6, # Paling ideal di layar large
xl=5 # Mungkin terlalu sempit, bisa disesuaikan
),
justify="center", # Tengahkan kolom di dalam baris
className="w-100" # Pastikan baris mengambil lebar penuh
)
]
)
# Callback untuk handle signup (TETAP SAMA SEPERTI SEBELUMNYA)
@callback(
Output('signup-url-redirect', 'pathname', allow_duplicate=True),
Output('signup-message', 'children', allow_duplicate=True),
Input('signup-button', 'n_clicks'),
State('signup-fullname', 'value'),
State('signup-username', 'value'),
State('signup-password', 'value'),
State('signup-confirm-password', 'value'),
State('signup-email', 'value'),
State('signup-role', 'value'),
prevent_initial_call=True
)
def handle_signup(n_clicks_signup, fullname, username, password, confirm_password, email, role):
if not fullname or not username or not password or not confirm_password or not role:
return no_update, dbc.Alert("Semua field (Nama Lengkap, Username, Password, Konfirmasi, Jabatan) harus diisi.", color="danger", dismissable=True, duration=4000)
if len(password) < 6:
return no_update, dbc.Alert("Password minimal 6 karakter.", color="danger", dismissable=True, duration=4000)
if password != confirm_password:
return no_update, dbc.Alert("Password dan konfirmasi password tidak cocok.", color="danger", dismissable=True, duration=4000)
username = username.strip()
email = email.strip() if email else None
with engine.connect() as conn:
stmt_check_username = select(users.c.id_user).where(users.c.username == username)
if conn.execute(stmt_check_username).fetchone():
return no_update, dbc.Alert(f"Username '{username}' sudah digunakan.", color="warning", dismissable=True, duration=4000)
if email:
stmt_check_email = select(users.c.id_user).where(users.c.email == email)
if conn.execute(stmt_check_email).fetchone():
return no_update, dbc.Alert(f"Email '{email}' sudah terdaftar.", color="warning", dismissable=True, duration=4000)
hashed_password_to_store = generate_password_hash(password)
# print(f"--- SIGNUP DEBUG: Username: {username}, Password Asli: '{password}', Hashed untuk DB: {hashed_password_to_store[:20]}... ---")
try:
new_user_values = {
'nama_lengkap': fullname.strip(),
'username': username,
'password': hashed_password_to_store,
'jabatan': role
}
if email:
new_user_values['email'] = email
insert_stmt = users.insert().values(**new_user_values)
conn.execute(insert_stmt)
conn.commit()
return '/login', dbc.Alert("Registrasi berhasil! Silakan login.", color="success", duration=5000)
except Exception as e:
conn.rollback()
# print(f"--- SIGNUP ERROR: {e} ---")
# import traceback
# traceback.print_exc()
return no_update, dbc.Alert(f"Terjadi kesalahan saat registrasi.", color="danger", dismissable=True, duration=4000)
return no_update, ""
layout = signup_layout