first commit
6
.streamlit/secrets.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[mysql]
|
||||
host = "localhost"
|
||||
port = 3306
|
||||
user = "root"
|
||||
password = ""
|
||||
dbname = "employee_attrition_db"
|
261
App.py
Normal file
@ -0,0 +1,261 @@
|
||||
import os
|
||||
import sys
|
||||
import streamlit as st
|
||||
|
||||
from pages.karyawan_form import show_karyawan_form
|
||||
from pages.pimpinan_form import show_pimpinan_form
|
||||
from pages.prediction import show_prediction
|
||||
from pages.report import show_report
|
||||
from pages.exploration import show_exploration
|
||||
from pages.karyawan_komen import show_karyawan_komen
|
||||
from pages.pimpinan_exploration import show_pimpinan_exploration
|
||||
|
||||
# Set konfigurasi halaman sebagai perintah pertama
|
||||
st.set_page_config(page_title="Streamlit Web App", layout="wide", initial_sidebar_state="collapsed")
|
||||
|
||||
# Tambahkan direktori 'App' ke sys.path
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Import halaman dari folder pages
|
||||
import pages as pg
|
||||
|
||||
# Import halaman dari root directory
|
||||
from login import show_login # Impor dari file login.py di root directory
|
||||
|
||||
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
logo_path = os.path.join(parent_dir, "asset/logo.png")
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<style>
|
||||
/* Hilangkan margin dan padding dari elemen utama */
|
||||
.stApp {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Pastikan elemen utama Streamlit tidak memiliki margin */
|
||||
.stMainBlockContainer {
|
||||
margin-top: 0 !important; /* Hilangkan jarak di atas */
|
||||
padding-top: 0 !important; /* Hilangkan padding atas */
|
||||
}
|
||||
|
||||
/* Hilangkan margin vertikal awal */
|
||||
[data-testid="stVerticalBlock"] {
|
||||
margin-top: 0 !important; /* Atur ulang margin atas */
|
||||
padding-top: 0 !important; /* Hilangkan padding atas */
|
||||
}
|
||||
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# CSS untuk mengubah warna latar belakang
|
||||
background_style = """
|
||||
<style>
|
||||
.stApp {
|
||||
background-color: #F9F9F9; /* Ubah warna latar belakang di sini */
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
st.markdown(background_style, unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<style>
|
||||
[data-testid="collapsedControl"] {
|
||||
display: none
|
||||
}
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
if "page" not in st.session_state:
|
||||
query_params = st.query_params
|
||||
print("Query parameters (raw):", repr(query_params))
|
||||
|
||||
if "page" in query_params:
|
||||
st.session_state.page = query_params["page"]
|
||||
if isinstance(st.session_state.page, list):
|
||||
st.session_state.page = st.session_state.page[0]
|
||||
st.session_state.page = st.session_state.page.strip()
|
||||
else:
|
||||
st.session_state.page = "Home"
|
||||
|
||||
# Tambahkan validasi login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
role = st.session_state.get('role', '').lower()
|
||||
role_pages = {
|
||||
'admin': 'admin',
|
||||
'karyawan': 'karyawan_form',
|
||||
'pimpinan': 'pimpinan_form'
|
||||
}
|
||||
st.session_state.page = role_pages.get(role, 'Login')
|
||||
|
||||
valid_pages = ["Home", "Login", "admin", "karyawan_form", "pimpinan_form",
|
||||
"exploration", "report", "Prediksi", "karyawan_komen", "pimpinan_exploration"]
|
||||
if st.session_state.page not in valid_pages:
|
||||
print(f"Invalid page detected: {st.session_state.page}. Defaulting to 'Home'.")
|
||||
st.session_state.page = "Home"
|
||||
print("Current page after query params handling:", st.session_state.page)
|
||||
|
||||
if 'logged_in' not in st.session_state:
|
||||
st.session_state['logged_in'] = False
|
||||
st.session_state['role'] = None
|
||||
print("Session state initialized:", st.session_state)
|
||||
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
def navbar_with_sidebar_control(pages, options, logo_path):
|
||||
# Kontrol sidebar visibility
|
||||
if not options.get("show_sidebar", True): # Jika "show_sidebar" = False
|
||||
st.markdown(
|
||||
"""
|
||||
<style>
|
||||
[data-testid="stSidebar"] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# Render navbar
|
||||
navbar_home()
|
||||
|
||||
def navbar_home():
|
||||
current_page = st.session_state.page
|
||||
print("Navbar current page:", repr(current_page))
|
||||
if current_page == "Login":
|
||||
return
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 0px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
align-items: center; /* Pusatkan teks secara vertikal */
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links .welcome-text {{
|
||||
color: #1D567E;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin: 0; /* Hilangkan margin default */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<!-- Ganti teks "Home" dengan teks statis -->
|
||||
<span class="welcome-text">Selamat Datang di Aplikasi Prediksi Retensi Karyawan</span>
|
||||
</div>
|
||||
<a class="login-button" href="?page=Login">Login</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# Atur Navbar dengan kontrol sidebar
|
||||
options = {
|
||||
"show_menu": False,
|
||||
"show_sidebar": False, # Ubah ke True jika ingin menampilkan sidebar
|
||||
}
|
||||
|
||||
# Atur Navbar dengan kontrol sidebar hanya untuk Home
|
||||
if st.session_state.page == "Home":
|
||||
navbar_with_sidebar_control(
|
||||
pages=["Home", "Login"],
|
||||
options=options,
|
||||
logo_path=logo_path,
|
||||
)
|
||||
|
||||
|
||||
# Deklarasi fungsi halaman
|
||||
functions = {
|
||||
"Home": pg.show_home,
|
||||
"Login": show_login, # Panggil fungsi show_login dari root directory
|
||||
"admin": show_prediction, # Pastikan fungsi ini diimpor dan didefinisikan
|
||||
"karyawan_form": show_karyawan_form, # Pastikan fungsi ini diimpor dan didefinisikan
|
||||
"pimpinan_form": show_pimpinan_form,
|
||||
"exploration": show_exploration,
|
||||
"Prediksi": show_prediction, # Tambahkan ini
|
||||
"report": show_report, # Tambahkan ini
|
||||
"karyawan_komen": show_karyawan_komen,
|
||||
"pimpinan_exploration": show_pimpinan_exploration
|
||||
}
|
||||
|
||||
query_params = st.query_params
|
||||
|
||||
# Validasi query parameter
|
||||
if "page" not in st.session_state:
|
||||
query_params = st.query_params
|
||||
print("Query parameters (raw):", repr(query_params))
|
||||
|
||||
if "page" in query_params and query_params["page"].strip() in functions.keys():
|
||||
st.session_state.page = query_params["page"].strip()
|
||||
else:
|
||||
st.session_state.page = "Home"
|
||||
|
||||
print(f"Query parameters: {query_params}")
|
||||
print(f"Session page: {st.session_state.page}")
|
||||
print(f"Calling function for page: {st.session_state.page}")
|
||||
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
role = st.session_state.get('role', '').lower()
|
||||
role_pages = {
|
||||
'admin': show_prediction,
|
||||
'karyawan': show_karyawan_form,
|
||||
'pimpinan': show_pimpinan_form
|
||||
}
|
||||
go_to = role_pages.get(role)
|
||||
if go_to:
|
||||
print(f"Calling function for logged-in page: {st.session_state.page}")
|
||||
go_to()
|
||||
else:
|
||||
go_to = functions.get(st.session_state.page)
|
||||
if go_to:
|
||||
print(f"Calling function for page: {st.session_state.page}")
|
||||
go_to()
|
||||
else:
|
||||
st.write("Halaman tidak ditemukan.")
|
25
LOGO.svg
Normal file
After Width: | Height: | Size: 2.2 MiB |
8093
X_train.csv
Normal file
BIN
__pycache__/login.cpython-39.pyc
Normal file
BIN
asset/logo.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
asset/main_img.png
Normal file
After Width: | Height: | Size: 121 KiB |
BIN
asset/news1.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
asset/news2.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
asset/news3.png
Normal file
After Width: | Height: | Size: 428 KiB |
BIN
asset/news4.png
Normal file
After Width: | Height: | Size: 1.7 MiB |
BIN
asset/news5.png
Normal file
After Width: | Height: | Size: 246 KiB |
BIN
asset/news6.png
Normal file
After Width: | Height: | Size: 1.9 MiB |
BIN
asset/news7.png
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
asset/news8.png
Normal file
After Width: | Height: | Size: 1.6 MiB |
BIN
asset/sitemap.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
clasification_model.sav
Normal file
218
login.py
Normal file
@ -0,0 +1,218 @@
|
||||
import streamlit as st
|
||||
import mysql.connector
|
||||
import os
|
||||
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
def navbar():
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "./asset/logo.png")
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px;
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
}}
|
||||
.navbar .text {{
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
<div class="text">Silahkan Login ke Akun Anda</div>
|
||||
</div>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# Fungsi untuk koneksi ke database
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
st.success("Koneksi ke database berhasil!")
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
# Fungsi untuk validasi login
|
||||
def validate_login(username, password):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
query = "SELECT * FROM user WHERE username = %s AND password = %s"
|
||||
cursor.execute(query, (username, password))
|
||||
user = cursor.fetchone()
|
||||
conn.close()
|
||||
return user
|
||||
return None
|
||||
|
||||
def show_login():
|
||||
navbar()
|
||||
st.markdown("""
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
|
||||
|
||||
/* Reset Layout */
|
||||
html, body, [data-testid="stAppViewContainer"] {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
/* Fullscreen Center */
|
||||
[data-testid="stAppViewContainer"] {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
/* Logo Styling */
|
||||
div[data-testid="stImage"] img {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Title Styling */
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 500;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
|
||||
/* Input Fields */
|
||||
.stTextInput > div > div > input {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.stButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Input form
|
||||
username = st.text_input("Username", placeholder="Masukkan username")
|
||||
password = st.text_input("Password", type="password", placeholder="Masukkan password")
|
||||
|
||||
# Validasi tombol login tetap tidak diubah
|
||||
if st.button("Login", key="login_button"):
|
||||
user = validate_login(username, password)
|
||||
if user:
|
||||
st.session_state['logged_in'] = True
|
||||
st.session_state['role'] = user['role'].lower()
|
||||
st.session_state['username'] = user['username']
|
||||
st.rerun()
|
||||
else:
|
||||
st.error("Username atau password salah.")
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True) # Tutup container
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
def main():
|
||||
if 'logged_in' not in st.session_state:
|
||||
st.session_state['logged_in'] = False
|
||||
st.session_state['role'] = None
|
||||
st.session_state['username'] = None
|
||||
|
||||
if st.session_state['logged_in']:
|
||||
role = st.session_state['role']
|
||||
if role == 'admin':
|
||||
st.switch_page("pages/prediction.py")
|
||||
elif role == 'karyawan':
|
||||
st.switch_page("pages/karyawan_form.py")
|
||||
elif role == 'pimpinan':
|
||||
st.switch_page("pages/pimpinan_form.py")
|
||||
else:
|
||||
show_login()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
9
pages/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
import os
|
||||
|
||||
print("Current directory:", os.getcwd())
|
||||
print("Files in pages directory:", os.listdir(os.path.dirname(__file__)))
|
||||
|
||||
from .home import show_home
|
||||
from .karyawan_form import show_karyawan_form
|
||||
from .pimpinan_form import show_pimpinan_form
|
||||
from .prediction import show_prediction
|
BIN
pages/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
pages/__pycache__/about.cpython-39.pyc
Normal file
BIN
pages/__pycache__/exploration.cpython-39.pyc
Normal file
BIN
pages/__pycache__/home.cpython-39.pyc
Normal file
BIN
pages/__pycache__/karyawan_form.cpython-39.pyc
Normal file
BIN
pages/__pycache__/karyawan_komen.cpython-39.pyc
Normal file
BIN
pages/__pycache__/login.cpython-39.pyc
Normal file
BIN
pages/__pycache__/pimpinan_exploration.cpython-39.pyc
Normal file
BIN
pages/__pycache__/pimpinan_form.cpython-39.pyc
Normal file
BIN
pages/__pycache__/prediction.cpython-39.pyc
Normal file
BIN
pages/__pycache__/report.cpython-39.pyc
Normal file
363
pages/exploration.py
Normal file
@ -0,0 +1,363 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import mysql.connector
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = "exploration" # Sesuaikan dengan halaman saat ini
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png") # Sesuaikan path logo
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=Prediksi" class="{ 'active' if st.session_state.page == 'Prediksi' else '' }">Prediksi</a>
|
||||
<a href="?page=exploration" class="{ 'active' if st.session_state.page == 'exploration' else '' }">Dashboard</a>
|
||||
<a href="?page=report" class="{ 'active' if st.session_state.page == 'report' else '' }">Laporan</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
def get_all_employee_data(filter_employee_id=None, filter_join_date=None):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = """
|
||||
SELECT employee_id AS ID_Karyawan, domisili as Domisili, jenis_kelamin as Jenis_Kelamin,
|
||||
join_date as Tanggal_Masuk, resign_date as Tanggal_Keluar, marriage_stat as Status_Pernikahan,
|
||||
dependant as Jumlah_Tanggungan, education as Pendidikan, absent_90D as Absen_90Hari,
|
||||
avg_time_work as Rata_Rata_Jam_Kerja, departemen as Departemen, position as Posisi,
|
||||
income as Penghasilan, total_komp as Total_Komplain, job_satisfaction as Kepuasan_Kerja,
|
||||
performance_rating as Kinerja_Kerja
|
||||
FROM data_employee_db WHERE 1=1"""
|
||||
params = []
|
||||
|
||||
if filter_employee_id:
|
||||
query += " AND employee_id LIKE %s"
|
||||
params.append(f"%{filter_employee_id}%")
|
||||
|
||||
if filter_join_date:
|
||||
query += " AND join_date >= %s"
|
||||
params.append(filter_join_date)
|
||||
|
||||
df = pd.read_sql(query, conn, params=params)
|
||||
return df
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengambil data: {e}")
|
||||
return pd.DataFrame()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_joined_employee_data(filter_employee_id=None):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = """
|
||||
SELECT
|
||||
e.employee_id as ID_Karyawan,
|
||||
e.comment AS Komplain,
|
||||
s.job_satisfaction AS Skor_Kepuasan_Kerja,
|
||||
s.performance_rating AS Skor_Kinerja_Kerja
|
||||
FROM employee_comments e
|
||||
INNER JOIN data_employee_db s ON e.employee_id = s.employee_id
|
||||
WHERE 1=1
|
||||
"""
|
||||
params = []
|
||||
|
||||
if filter_employee_id:
|
||||
query += " AND e.employee_id LIKE %s"
|
||||
params.append(f"%{filter_employee_id}%")
|
||||
|
||||
df = pd.read_sql(query, conn, params=params)
|
||||
return df
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengambil data: {e}")
|
||||
return pd.DataFrame()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def show_exploration():
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stDownloadButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stDownloadButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Halaman Dashboard
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Dropdown menu
|
||||
menu_option = st.selectbox(
|
||||
"Pilih Menu",
|
||||
["Lihat Semua Informasi Karyawan", "Lihat Data Komplain Karyawan"]
|
||||
)
|
||||
|
||||
# Pilihan menu
|
||||
if menu_option == "Lihat Semua Informasi Karyawan":
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
filter_employee_id = st.text_input("Filter berdasarkan ID Karyawan", placeholder="Contoh: EM12345")
|
||||
|
||||
with col2:
|
||||
filter_join_date = st.date_input("Filter berdasarkan Tanggal Masuk", value=None)
|
||||
|
||||
df = get_all_employee_data(filter_employee_id, filter_join_date)
|
||||
|
||||
if not df.empty:
|
||||
st.dataframe(df)
|
||||
csv = df.to_csv(index=False)
|
||||
st.download_button(
|
||||
label="Download Data sebagai CSV",
|
||||
data=csv,
|
||||
file_name="data_employee_db.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.write("Tidak ada data yang tersedia di tabel data_employee_db.")
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<h4 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Analisis Visual Distribusi Karyawan
|
||||
</h4>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Layout untuk dua grafik per baris
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.markdown(
|
||||
"""
|
||||
<h6 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Distribusi Karyawan Berdasarkan Status Pernikahan
|
||||
</h6>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
fig_marriage = px.histogram(
|
||||
df,
|
||||
x="Status_Pernikahan",
|
||||
labels={"Status_Pernikahan": "Status Pernikahan", "count": "Jumlah Karyawan"}
|
||||
)
|
||||
fig_marriage.update_layout(xaxis_title="Status Pernikahan", yaxis_title="Jumlah")
|
||||
st.plotly_chart(fig_marriage, use_container_width=True)
|
||||
|
||||
with col2:
|
||||
st.markdown(
|
||||
"""
|
||||
<h6 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Distribusi Karyawan Berdasarkan Pendidikan
|
||||
</h6>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
fig_education = px.histogram(
|
||||
df,
|
||||
x="Pendidikan",
|
||||
labels={"Pendidikan": "Pendidikan", "count": "Jumlah Karyawan"}
|
||||
)
|
||||
fig_education.update_layout(xaxis_title="Pendidikan", yaxis_title="Jumlah")
|
||||
st.plotly_chart(fig_education, use_container_width=True)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.markdown(
|
||||
"""
|
||||
<h6 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Distribusi Karyawan Berdasarkan Domisili
|
||||
</h6>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
fig_domisili = px.histogram(
|
||||
df,
|
||||
x="Domisili",
|
||||
labels={"Domisili": "Domisili", "count": "Jumlah Karyawan"}
|
||||
)
|
||||
fig_domisili.update_layout(xaxis_title="Domisili", yaxis_title="Jumlah")
|
||||
st.plotly_chart(fig_domisili, use_container_width=True)
|
||||
|
||||
with col2:
|
||||
st.markdown(
|
||||
"""
|
||||
<h6 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Distribusi Karyawan Berdasarkan Departemen
|
||||
</h6>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
fig_department = px.histogram(
|
||||
df,
|
||||
x="Departemen",
|
||||
labels={"Departemen": "Departemen", "count": "Jumlah Karyawan"}
|
||||
)
|
||||
fig_department.update_layout(xaxis_title="Departemen", yaxis_title="Jumlah")
|
||||
st.plotly_chart(fig_department, use_container_width=True)
|
||||
|
||||
elif menu_option == "Lihat Data Komplain Karyawan":
|
||||
|
||||
filter_employee_id = st.text_input("Filter berdasarkan ID Karyawan", placeholder="Contoh: EM12345")
|
||||
|
||||
df = get_joined_employee_data(filter_employee_id)
|
||||
|
||||
if not df.empty:
|
||||
st.dataframe(df)
|
||||
|
||||
# Tombol download
|
||||
csv = df.to_csv(index=False)
|
||||
st.download_button(
|
||||
label="Download Data sebagai CSV",
|
||||
data=csv,
|
||||
file_name="joined_employee_data.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.write("Tidak ada data gabungan yang tersedia.")
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Jalankan fungsi show_exploration
|
||||
if __name__ == "__main__":
|
||||
show_exploration()
|
291
pages/home.py
Normal file
@ -0,0 +1,291 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import base64
|
||||
|
||||
def show_home():
|
||||
# Atur layout halaman
|
||||
st.markdown(
|
||||
"""
|
||||
<style>
|
||||
/* Hapus padding dan margin default Streamlit */
|
||||
.stApp {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Hapus padding dan margin dari semua elemen */
|
||||
.center-content, .about-section, .content-section, .news-section, .footer {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Atur layout utama */
|
||||
.center-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.image-content {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.image-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Tentang Aplikasi */
|
||||
.about-section {
|
||||
background-color: #FFFFFF;
|
||||
padding: 30px !important;
|
||||
border-radius: 15px;
|
||||
margin-top: 50px !important;
|
||||
margin-bottom: 50px !important;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.about-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: #BBE2F2;
|
||||
border-radius: 20px;
|
||||
top: 25px;
|
||||
}
|
||||
|
||||
/* Bagian Aplikasi */
|
||||
.content-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin-bottom: 50px; /* Tambahkan margin-bottom untuk memberikan jarak */
|
||||
/* position: relative; */
|
||||
}
|
||||
|
||||
.content-section::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -40px;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: #BBE2F2;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.content-section .text-content {
|
||||
flex: 1;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
color: #555555;
|
||||
line-height: 1.6;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.content-section .image-content img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.about-section h2,
|
||||
.content-section h2 {
|
||||
font-family: 'Poppins', sans-serif; /* Gunakan font Poppins */
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.about-section p,
|
||||
.content-section .text-content p {
|
||||
font-family: 'Poppins', sans-serif; /* Gunakan font Poppins */
|
||||
font-size: 16px;
|
||||
color: #555555;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Judul Bagian Aplikasi */
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #333333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* Berita dan Informasi */
|
||||
.news-section {
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.news-title {
|
||||
font-size: 30px !important;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.news-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.news-grid img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Path ke gambar
|
||||
parent_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
image_path = os.path.join(parent_dir, "../asset/main_img.png")
|
||||
|
||||
# Render konten utama
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="center-content">
|
||||
<div class="text-content">
|
||||
<p><strong>Aplikasi Prediksi Retensi Karyawan</strong></p>
|
||||
</div>
|
||||
<div class="image-content">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(image_path)}" alt="Gambar Retensi Karyawan">
|
||||
</div>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Tentang Aplikasi
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="about-section">
|
||||
<h2>Tentang Aplikasi</h2>
|
||||
<p>
|
||||
Aplikasi ini dirancang untuk memprediksi retensi karyawan dengan memanfaatkan analisis data dan pembelajaran mesin.
|
||||
Dikembangkan sebagai Tugas Akhir guna memperoleh gelar Sarjana Komputer di Universitas Pembangunan Nasional Veteran Jakarta (UPNVJ),
|
||||
aplikasi ini bertujuan membantu perusahaan dalam mengidentifikasi dan mempertahankan karyawan berkualitas👩🏻🎓.
|
||||
</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Bagian Aplikasi
|
||||
image_path_2 = os.path.join(parent_dir, "../asset/sitemap.png")
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="content-section">
|
||||
<div class="image-content">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(image_path_2)}" alt="Gambar Konten">
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<h2 class="section-title">Bagian Aplikasi</h2>
|
||||
<p style="text-align: justify;">
|
||||
Aplikasi ini dirancang untuk mendukung prediksi retensi karyawan dan pengelolaan data dengan fitur-fitur yang terstruktur berdasarkan peran pengguna.
|
||||
Halaman login menjadi pintu masuk utama untuk autentikasi, setelah itu pengguna diarahkan ke halaman sesuai perannya: admin, karyawan, atau pimpinan.
|
||||
Admin memiliki akses ke halaman prediksi untuk analisis data, dashboard untuk memantau statistik, dan halaman laporan untuk melihat detail data.
|
||||
Karyawan dapat mengisi form kepuasan kerja untuk memberikan umpan balik terkait pengalaman mereka.
|
||||
Sementara itu, pimpinan dapat menggunakan dashboard untuk melihat data strategis serta mengisi form penilaian kinerja guna mengevaluasi performa karyawan.
|
||||
Aplikasi ini dirancang untuk mempermudah pengelolaan dan pengambilan keputusan berbasis data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Berita dan Informasi
|
||||
st.markdown(
|
||||
f"""
|
||||
<div class="news-section">
|
||||
<p class="news-title">Berita dan Informasi</p>
|
||||
<div class="news-grid">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news1.png'))}" alt="Berita 1">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news2.png'))}" alt="Berita 2">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news3.png'))}" alt="Berita 3">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news4.png'))}" alt="Berita 4">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news5.png'))}" alt="Berita 5">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news6.png'))}" alt="Berita 6">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news7.png'))}" alt="Berita 7">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(os.path.join(parent_dir, '../asset/news8.png'))}" alt="Berita 8">
|
||||
</div>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
|
||||
# Jalankan aplikasi
|
||||
if __name__ == "__main__":
|
||||
show_home()
|
300
pages/karyawan_form.py
Normal file
@ -0,0 +1,300 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import mysql.connector
|
||||
import math
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = st.session_state.get("page", "Home")
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png")
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=karyawan_form" class="{ 'active' if st.session_state.page == 'karyawan_form' else '' }">Form Kepuasan Kerja</a>
|
||||
<a href="?page=karyawan_komen" class="{ 'active' if st.session_state.page == 'karyawan_komen' else '' }">Form Komplain</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
def check_employee_in_db(employee_id):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
query = "SELECT * FROM data_employee_db WHERE employee_id = %s"
|
||||
cursor.execute(query, (employee_id,))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
return result # Mengembalikan data karyawan jika ditemukan
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengakses database: {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def save_job_satisfaction(employee_id, responses):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
# Hitung skor akhir
|
||||
total_score = sum([responses[q] for q in range(len(responses))])
|
||||
average_score = total_score / len(responses)
|
||||
final_score = min(max(round(average_score), 1), 4) # Batas 1-4
|
||||
|
||||
# Simpan ke tabel employee_job_sas
|
||||
cursor = conn.cursor()
|
||||
query = """
|
||||
INSERT INTO employee_job_sas (
|
||||
employee_id, question_1, question_2, question_3, question_4,
|
||||
question_5, question_6, question_7, question_8, skor_akhir
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(query, (employee_id, *responses, final_score))
|
||||
conn.commit()
|
||||
|
||||
# Update kolom job_satisfaction di tabel data_employee_db
|
||||
update_query = """
|
||||
UPDATE data_employee_db
|
||||
SET job_satisfaction = %s
|
||||
WHERE employee_id = %s
|
||||
"""
|
||||
cursor.execute(update_query, (final_score, employee_id))
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
return final_score
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat menyimpan data: {e}")
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
|
||||
def show_karyawan_form():
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Form Kepuasan Kerja Karyawan
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Inisialisasi session state
|
||||
if "employee_id" not in st.session_state:
|
||||
st.session_state.employee_id = None # Awalnya kosong
|
||||
|
||||
if "submit_success" not in st.session_state:
|
||||
st.session_state.submit_success = False # Awalnya False
|
||||
|
||||
# Input Employee ID
|
||||
if not st.session_state.employee_id: # Jika belum ada ID yang tersimpan
|
||||
employee_id = st.text_input("Masukkan ID Karyawan", placeholder="Contoh: EM12345")
|
||||
|
||||
# Tombol untuk mencocokkan employee_id
|
||||
if st.button("Isi Form"):
|
||||
if not employee_id:
|
||||
st.error("Harap masukkan Employee ID terlebih dahulu.")
|
||||
return
|
||||
|
||||
# Cek apakah employee_id ada di database
|
||||
employee_data = check_employee_in_db(employee_id)
|
||||
if not employee_data:
|
||||
st.error("Employee ID tidak ditemukan di database.")
|
||||
return
|
||||
|
||||
# Jika ditemukan, simpan Employee ID di session_state
|
||||
st.session_state.employee_id = employee_id
|
||||
|
||||
# Jika Employee ID sudah disimpan
|
||||
if st.session_state.employee_id:
|
||||
st.success(f"Employee ID ditemukan!")
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Isi Form Kepuasan Kerja
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
questions = [
|
||||
"Bagaimana tingkat kepuasan Anda terhadap lingkungan kerja saat ini?",
|
||||
"Seberapa puas Anda dengan keseimbangan antara pekerjaan dan kehidupan pribadi?",
|
||||
"Bagaimana pendapat Anda tentang kompensasi atau benefit yang Anda terima?",
|
||||
"Bagaimana penilaian Anda terhadap hubungan dengan rekan kerja?",
|
||||
"Seberapa puas Anda dengan kesempatan pengembangan karier atau pelatihan yang disediakan perusahaan?",
|
||||
"Bagaimana penilaian Anda terhadap kejelasan tugas dan tanggung jawab di pekerjaan Anda?",
|
||||
"Seberapa puas Anda dengan penghargaan atau pengakuan atas pencapaian kerja Anda?",
|
||||
"Bagaimana tingkat kepuasan Anda terhadap kepemimpinan dan arahan dari manajer Anda?"
|
||||
]
|
||||
|
||||
options = {"Sangat Tidak Puas": 1, "Tidak Puas": 2, "Puas": 3, "Sangat Puas": 4}
|
||||
|
||||
# Buat dua kolom
|
||||
col1, col2 = st.columns(2)
|
||||
responses = []
|
||||
|
||||
for idx, question in enumerate(questions):
|
||||
if idx % 2 == 0:
|
||||
with col1:
|
||||
response = st.selectbox(question, list(options.keys()), key=f"q{idx}")
|
||||
responses.append(options[response])
|
||||
else:
|
||||
with col2:
|
||||
response = st.selectbox(question, list(options.keys()), key=f"q{idx}")
|
||||
responses.append(options[response])
|
||||
|
||||
# Tombol Submit
|
||||
if st.button("Submit") and not st.session_state.submit_success:
|
||||
final_score = save_job_satisfaction(st.session_state.employee_id, responses)
|
||||
if final_score is not None:
|
||||
st.session_state.submit_success = True # Tandai data berhasil disimpan
|
||||
st.success(f"Data berhasil disimpan! Skor Akhir: {final_score}")
|
||||
|
||||
# Jika data berhasil disimpan, tampilkan tombol kembali
|
||||
if st.session_state.submit_success:
|
||||
if st.button("Kembali ke Form Awal"):
|
||||
# Reset Employee ID dan status submit
|
||||
st.session_state.employee_id = None
|
||||
st.session_state.submit_success = False
|
||||
st.rerun()
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
if __name__ == "__main__":
|
||||
show_karyawan_form()
|
252
pages/karyawan_komen.py
Normal file
@ -0,0 +1,252 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import mysql.connector
|
||||
import time
|
||||
|
||||
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk koneksi ke database
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
# Fungsi untuk menyimpan data ke database
|
||||
def save_employee_comment(employee_id, comment):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = """
|
||||
INSERT INTO employee_comments (employee_id, comment, created_at)
|
||||
VALUES (%s, %s, NOW())
|
||||
"""
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, (employee_id, comment))
|
||||
conn.commit()
|
||||
st.success("Komentar berhasil disimpan. Terima kasih atas masukan Anda!")
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat menyimpan komentar: {e}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def update_total_comments(employee_id):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
# Hitung jumlah komentar dari employee_comments berdasarkan employee_id
|
||||
cursor = conn.cursor()
|
||||
query_count = """
|
||||
SELECT COUNT(*) FROM employee_comments WHERE employee_id = %s
|
||||
"""
|
||||
cursor.execute(query_count, (employee_id,))
|
||||
total_comments = cursor.fetchone()[0]
|
||||
|
||||
# Update total_komp di data_employee_db
|
||||
query_update = """
|
||||
UPDATE data_employee_db
|
||||
SET total_komp = %s
|
||||
WHERE employee_id = %s
|
||||
"""
|
||||
cursor.execute(query_update, (total_comments, employee_id))
|
||||
conn.commit()
|
||||
st.success(f"Total komentar untuk Employee ID {employee_id} berhasil diperbarui: {total_comments}")
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat memperbarui total komentar: {e}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = st.session_state.get("page", "Home")
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png")
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=karyawan_form" class="{ 'active' if st.session_state.page == 'karyawan_form' else '' }">Form Kepuasan Kerja</a>
|
||||
<a href="?page=karyawan_komen" class="{ 'active' if st.session_state.page == 'karyawan_komen' else '' }">Form Komplain</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
def show_karyawan_komen():
|
||||
# Navbar
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Form Komplain
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Inisialisasi session state
|
||||
if "submit_success" not in st.session_state:
|
||||
st.session_state.submit_success = False
|
||||
if "employee_id" not in st.session_state:
|
||||
st.session_state.employee_id = ""
|
||||
|
||||
# Jika data berhasil disimpan, tampilkan pesan sukses dan tombol kembali
|
||||
if st.session_state.submit_success:
|
||||
st.success("Komentar berhasil disimpan. Terima kasih atas masukan Anda!")
|
||||
if st.button("Kembali ke Form Awal"):
|
||||
st.session_state.submit_success = False
|
||||
st.session_state.employee_id = ""
|
||||
st.rerun()
|
||||
return # Penting: hentikan eksekusi fungsi di sini
|
||||
|
||||
# Form input (hanya ditampilkan jika submit_success adalah False)
|
||||
employee_id = st.text_input(
|
||||
"Masukkan Employee ID Anda",
|
||||
value=st.session_state.employee_id,
|
||||
placeholder="Contoh: EM12345"
|
||||
)
|
||||
|
||||
comment = st.text_area("Tuliskan Komentar atau Keluhan Anda")
|
||||
|
||||
if st.button("Submit"):
|
||||
if not employee_id or not comment:
|
||||
st.error("Harap isi semua bidang sebelum mengirimkan.")
|
||||
else:
|
||||
save_employee_comment(employee_id, comment)
|
||||
update_total_comments(employee_id)
|
||||
st.session_state.submit_success = True
|
||||
st.session_state.employee_id = employee_id
|
||||
st.rerun() # Gunakan experimental_rerun() untuk memuat ulang halaman
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
# Jalankan fungsi show_pimpinan_form
|
||||
if __name__ == "__main__":
|
||||
show_karyawan_komen()
|
276
pages/pimpinan_exploration.py
Normal file
@ -0,0 +1,276 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import pandas as pd
|
||||
import mysql.connector
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = st.session_state.get("page", "Home")
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png")
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=pimpinan_form" class="{ 'active' if st.session_state.page == 'pimpinan_form' else '' }">Form Performance Rate</a>
|
||||
<a href="?page=pimpinan_exploration" class="{ 'active' if st.session_state.page == 'pimpinan_exploration' else '' }">Dashboard</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
# Fungsi untuk mendapatkan data "All Data + Komentar"
|
||||
def get_all_data_with_comments():
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = """
|
||||
SELECT
|
||||
e.employee_id AS ID_Karyawan, e.domisili as Domisili, e.jenis_kelamin as Jenis_Kelamin,
|
||||
e.join_date as Tanggal_Masuk, e.resign_date as Tanggal_Keluar, e.marriage_stat as Status_Pernikahan,
|
||||
e.dependant as Jumlah_Tanggungan, e.education as Pendidikan, e.absent_90D as Absen_90Hari,
|
||||
e.avg_time_work as Rata_Rata_Jam_Kerja, e.departemen as Departemen, e.position as Posisi,
|
||||
e.income as Penghasilan, e.total_komp as Total_Komplain, e.job_satisfaction as Kepuasan_Kerja,
|
||||
e.performance_rating as Kinerja_Kerja,
|
||||
IFNULL(c.comment, 'Tidak ada Komplain') AS Isi_Komentar
|
||||
FROM data_employee_db e
|
||||
LEFT JOIN employee_comments c ON e.employee_id = c.employee_id
|
||||
"""
|
||||
df = pd.read_sql(query, conn)
|
||||
return df
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengambil data: {e}")
|
||||
return pd.DataFrame()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# Fungsi untuk mendapatkan data "History Prediksi"
|
||||
def get_history_prediction():
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = """SELECT employee_id as ID_Karyawan, hasil_prediksi_klasifikasi as Hasil_Prediksi_Retensi,
|
||||
hasil_prediksi_regresi as Hasil_Prediksi_Lama_Kerja, waktu_prediksi as Waktu_Prediksi
|
||||
FROM history_prediction"""
|
||||
df = pd.read_sql(query, conn)
|
||||
return df
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengambil data: {e}")
|
||||
return pd.DataFrame()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def show_pimpinan_exploration():
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stDownloadButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stDownloadButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Dashboard
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Menu Dropdown
|
||||
menu_option = st.selectbox(
|
||||
"Pilih data yang ingin ditampilkan:",
|
||||
["Semua Data Karyawan", "Histori Prediksi"]
|
||||
)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
filter_employee_id = st.text_input("Filter berdasarkan Karyawan_ID", placeholder="Contoh: EM12345")
|
||||
with col2:
|
||||
filter_departemen = st.selectbox(
|
||||
"Filter berdasarkan Departemen",
|
||||
["Semua", "Engineering & IT", "Service & Support", "Creative & Design",
|
||||
"Marketing", "Operations", "HR", "Finance & Accounting",
|
||||
"Corporate Strategy & Communications"]
|
||||
)
|
||||
|
||||
if menu_option == "Semua Data Karyawan":
|
||||
df = get_all_data_with_comments()
|
||||
|
||||
if not df.empty:
|
||||
# Terapkan filter
|
||||
if filter_employee_id:
|
||||
df = df[df['Karyawan_ID'].str.contains(filter_employee_id, na=False)]
|
||||
|
||||
if filter_departemen != "Semua":
|
||||
df = df[df['Departemen'] == filter_departemen]
|
||||
|
||||
# Tampilkan data
|
||||
st.dataframe(df)
|
||||
|
||||
# Tombol untuk download CSV
|
||||
csv = df.to_csv(index=False)
|
||||
st.download_button(
|
||||
label="Download Tabel sebagai CSV",
|
||||
data=csv,
|
||||
file_name="all_data_with_comments.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.write("Tidak ada data yang tersedia.")
|
||||
|
||||
elif menu_option == "Histori Prediksi":
|
||||
df = get_history_prediction()
|
||||
|
||||
if not df.empty:
|
||||
# Terapkan filter
|
||||
if filter_employee_id:
|
||||
df = df[df['Karyawan_ID'].str.contains(filter_employee_id, na=False)]
|
||||
|
||||
# Tampilkan data
|
||||
st.dataframe(df)
|
||||
|
||||
# Tombol untuk download CSV
|
||||
csv = df.to_csv(index=False)
|
||||
st.download_button(
|
||||
label="Download Tabel sebagai CSV",
|
||||
data=csv,
|
||||
file_name="history_prediction.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.write("Tidak ada data yang tersedia.")
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
# Jalankan fungsi show_pimpinan_exploration
|
||||
if __name__ == "__main__":
|
||||
show_pimpinan_exploration()
|
309
pages/pimpinan_form.py
Normal file
@ -0,0 +1,309 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import mysql.connector
|
||||
import math
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = st.session_state.get("page", "Home")
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png")
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=pimpinan_form" class="{ 'active' if st.session_state.page == 'pimpinan_form' else '' }">Form Performance Rate</a>
|
||||
<a href="?page=pimpinan_exploration" class="{ 'active' if st.session_state.page == 'pimpinan_exploration' else '' }">Dashboard</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
# Fungsi untuk koneksi ke database
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
# Fungsi untuk mencocokkan employee_id di database
|
||||
def check_employee_in_db(employee_id):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
query = "SELECT * FROM data_employee_db WHERE employee_id = %s"
|
||||
cursor.execute(query, (employee_id,))
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
return result # Mengembalikan data karyawan jika ditemukan
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengakses database: {e}")
|
||||
return None
|
||||
return None
|
||||
|
||||
# Fungsi untuk menyimpan data dan menghitung skor akhir
|
||||
def save_employee_ratings(employee_id, responses):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
# Hitung skor akhir
|
||||
total_score = sum([responses[q] for q in range(len(responses))])
|
||||
average_score = total_score / len(responses)
|
||||
final_score = min(max(round(average_score), 1), 4) # Batas 1-4
|
||||
|
||||
# Simpan ke tabel employee_ratings
|
||||
cursor = conn.cursor()
|
||||
query = """
|
||||
INSERT INTO employee_ratings (
|
||||
employee_id, question_1, question_2, question_3, question_4,
|
||||
question_5, question_6, question_7, question_8, skor_akhir
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(query, (employee_id, *responses, final_score))
|
||||
conn.commit()
|
||||
|
||||
# Update performance_rating di data_employee_db
|
||||
update_query = """
|
||||
UPDATE data_employee_db
|
||||
SET performance_rating = %s
|
||||
WHERE employee_id = %s
|
||||
"""
|
||||
cursor.execute(update_query, (final_score, employee_id))
|
||||
conn.commit()
|
||||
|
||||
conn.close()
|
||||
return final_score
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat menyimpan data: {e}")
|
||||
conn.close()
|
||||
return None
|
||||
|
||||
# Fungsi utama untuk form kinerja rating karyawan
|
||||
def show_pimpinan_form():
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Form Kinerja Rating Karyawan
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Inisialisasi session state
|
||||
if "employee_id" not in st.session_state:
|
||||
st.session_state.employee_id = None # Awalnya kosong
|
||||
|
||||
if "submit_success" not in st.session_state:
|
||||
st.session_state.submit_success = False # Awalnya False
|
||||
|
||||
# Input Employee ID
|
||||
if not st.session_state.employee_id: # Jika belum ada ID yang tersimpan
|
||||
employee_id = st.text_input("Masukkan Employee ID Karyawan", placeholder="Contoh: 12345")
|
||||
|
||||
# Tombol untuk mencocokkan employee_id
|
||||
if st.button("Isi Form"):
|
||||
if not employee_id:
|
||||
st.error("Harap masukkan Employee ID terlebih dahulu.")
|
||||
return
|
||||
|
||||
# Cek apakah employee_id ada di database
|
||||
employee_data = check_employee_in_db(employee_id)
|
||||
if not employee_data:
|
||||
st.error("Employee ID tidak ditemukan di database.")
|
||||
return
|
||||
|
||||
# Jika ditemukan, simpan Employee ID di session_state
|
||||
st.session_state.employee_id = employee_id
|
||||
|
||||
# Jika Employee ID sudah disimpan
|
||||
if st.session_state.employee_id:
|
||||
st.success(f"Karyawan_ID ditemukan! Anda sedang mengisi untuk ID: {st.session_state.employee_id}")
|
||||
|
||||
# Pertanyaan penilaian (2 kolom)
|
||||
st.markdown(
|
||||
"""
|
||||
<h5 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Isi Form Kinerja Karyawan
|
||||
</h5>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
questions = [
|
||||
"Bagaimana tingkat keandalan karyawan dalam menyelesaikan tugas tepat waktu?",
|
||||
"Seberapa efektif karyawan ini dalam bekerja secara mandiri atau dalam tim?",
|
||||
"Bagaimana penilaian Anda terhadap kemampuan karyawan untuk memecahkan masalah?",
|
||||
"Seberapa baik karyawan ini dalam mencapai target atau KPI yang telah ditentukan?",
|
||||
"Seberapa baik karyawan ini beradaptasi terhadap perubahan atau tantangan baru?",
|
||||
"Bagaimana penilaian Anda terhadap inisiatif yang diambil oleh karyawan ini dalam pekerjaannya?",
|
||||
"Seberapa efektif komunikasi karyawan ini, baik kepada rekan kerja maupun kepada pelanggan?",
|
||||
"Bagaimana Anda menilai sikap profesionalisme karyawan dalam menjalankan tugas sehari-hari?"
|
||||
]
|
||||
|
||||
options = {"Sangat Buruk": 1, "Cukup Baik": 2, "Baik": 3, "Sangat Baik": 4}
|
||||
|
||||
# Buat dua kolom
|
||||
col1, col2 = st.columns(2)
|
||||
responses = []
|
||||
|
||||
for idx, question in enumerate(questions):
|
||||
if idx % 2 == 0:
|
||||
with col1:
|
||||
response = st.selectbox(question, list(options.keys()), key=f"q{idx}")
|
||||
responses.append(options[response])
|
||||
else:
|
||||
with col2:
|
||||
response = st.selectbox(question, list(options.keys()), key=f"q{idx}")
|
||||
responses.append(options[response])
|
||||
|
||||
|
||||
# Tombol Submit
|
||||
if st.button("Submit") and not st.session_state.submit_success:
|
||||
final_score = save_employee_ratings(st.session_state.employee_id, responses)
|
||||
if final_score is not None:
|
||||
st.session_state.submit_success = True # Tandai data berhasil disimpan
|
||||
st.success(f"Data berhasil disimpan! Skor Akhir: {final_score} untuk Karyawan_ID: {st.session_state.employee_id}")
|
||||
|
||||
# Jika data berhasil disimpan, tampilkan tombol kembali
|
||||
if st.session_state.submit_success:
|
||||
if st.button("Kembali ke Form Awal"):
|
||||
# Reset Employee ID dan status submit
|
||||
st.session_state.employee_id = None
|
||||
st.session_state.submit_success = False
|
||||
st.rerun()
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Jalankan fungsi show_pimpinan_form
|
||||
if __name__ == "__main__":
|
||||
show_pimpinan_form()
|
501
pages/prediction.py
Normal file
@ -0,0 +1,501 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import pickle
|
||||
import pandas as pd
|
||||
import shap
|
||||
import matplotlib.pyplot as plt
|
||||
from catboost import Pool
|
||||
import numpy as np
|
||||
import mysql.connector
|
||||
import io
|
||||
import json
|
||||
|
||||
reg_model = pickle.load(open('regression_model.sav', 'rb'))
|
||||
class_model = pickle.load(open('clasification_model.sav', 'rb'))
|
||||
train_file_path = 'X_train.csv'
|
||||
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
# Fungsi untuk mendapatkan data karyawan dari database
|
||||
def get_employee_data_from_db(employee_id):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor(dictionary=True)
|
||||
query = "SELECT * FROM data_employee_db WHERE employee_id = %s"
|
||||
cursor.execute(query, (employee_id,))
|
||||
result = cursor.fetchone()
|
||||
return result # Kembalikan data sebagai dictionary jika ditemukan, None jika tidak
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengakses database: {e}")
|
||||
return None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def process_employee_data(df):
|
||||
numeric_columns = [
|
||||
"job_satisfaction", "performance_rating", "absent_90D", "income", "dependant",
|
||||
"avg_time_work", "total_komp"
|
||||
]
|
||||
|
||||
for col in numeric_columns:
|
||||
if col in df.columns:
|
||||
df[col] = pd.to_numeric(df[col], errors="coerce")
|
||||
|
||||
end_date = pd.to_datetime("2024-10-31")
|
||||
df["date_of_birth"] = pd.to_datetime(df["date_of_birth"], errors='coerce')
|
||||
df["age_years"] = (end_date - df["date_of_birth"]).dt.days // 365
|
||||
|
||||
df["join_date"] = pd.to_datetime(df["join_date"])
|
||||
df["resign_date"] = pd.to_datetime(df["resign_date"])
|
||||
|
||||
df["resign_date"].fillna(end_date, inplace=True)
|
||||
|
||||
df["total_komp"].fillna(0, inplace=True)
|
||||
df["absent_90D"].fillna(0, inplace=True)
|
||||
|
||||
df["active_work"] = (df["resign_date"] - df["join_date"]).dt.days
|
||||
|
||||
df["active_work_months"] = df["active_work"] // 30
|
||||
df["income_3_months"] = df["income"] * 3
|
||||
df["income_6_months"] = df["income"] * 6
|
||||
df["total_income_work"] = df["income"] * df["active_work_months"]
|
||||
|
||||
df["absence_ratio"] = df["absent_90D"] / (df["active_work"] / 90)
|
||||
df["income_dependant_ratio"] = df["income"] / (df["dependant"] + 1)
|
||||
df["work_efficiency"] = df["avg_time_work"] / 8
|
||||
|
||||
def categorize_work_duration_months(months):
|
||||
if months < 12:
|
||||
return "Short-term"
|
||||
elif 12 <= months <= 36:
|
||||
return "Mid-term"
|
||||
else:
|
||||
return "Long-term"
|
||||
|
||||
df['active_work_category'] = df['active_work_months'].apply(categorize_work_duration_months)
|
||||
|
||||
# Work Stability Score
|
||||
df['work_stability_score'] = df['active_work_months'] / (df['absent_90D'] + 1)
|
||||
|
||||
# Married-Dependent Ratio
|
||||
def married_dependent_ratio(row):
|
||||
if row['marriage_stat'] == 'Married':
|
||||
return row['dependant'] + 1
|
||||
else:
|
||||
return 1
|
||||
|
||||
df['married_dependent_ratio'] = df.apply(married_dependent_ratio, axis=1)
|
||||
|
||||
# Job Income to Position Score
|
||||
position_score_mapping = {'Junior': 2, 'Staff': 1, 'Senior': 3, 'Manager': 4}
|
||||
df['position_score'] = df['position'].map(position_score_mapping)
|
||||
df['job_income_position_score'] = df['income'] / df['position_score']
|
||||
|
||||
# Education-Adjusted Income
|
||||
education_score_mapping = {'SLTA': 1, 'D1': 2, 'D2': 3, 'D3': 4, 'S1': 5, 'S2': 6, 'S3': 7}
|
||||
df['education_score'] = df['education'].map(education_score_mapping)
|
||||
df['education_income_ratio'] = df['income'] / df['education_score']
|
||||
|
||||
# Weighted Satisfaction-Performance Score
|
||||
df['weighted_satisfaction_performance'] = (
|
||||
0.6 * df['job_satisfaction'] + 0.4 * df['performance_rating']
|
||||
)
|
||||
|
||||
# Resign Risk Indicator
|
||||
def resign_risk_indicator(row):
|
||||
if row['age_years'] < 30 and row['active_work_months'] < 12:
|
||||
return "High"
|
||||
elif 1 <= row['active_work_months'] <= 36:
|
||||
return "Medium"
|
||||
else:
|
||||
return "Low"
|
||||
|
||||
df['resign_risk_indicator'] = df.apply(resign_risk_indicator, axis=1)
|
||||
|
||||
# Adjusted Work Time
|
||||
df['adjusted_work_time'] = df['avg_time_work'] * (1 - (df['absent_90D'] / ((df['active_work_months'] * 90) + 1)))
|
||||
|
||||
job_satisfaction_mapping = {1.0: 'Low', 2.0: 'Medium', 3.0: 'High', 4.0: 'Very High'}
|
||||
df['job_satisfaction'] = df['job_satisfaction'].map(job_satisfaction_mapping)
|
||||
|
||||
performance_rating_mapping = {1.0: 'Low', 2.0: 'Good', 3.0: 'Excellent', 4.0: 'Outstanding'}
|
||||
df['performance_rating'] = df['performance_rating'].map(performance_rating_mapping)
|
||||
|
||||
return df
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = st.session_state.get("page", "Home")
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png")
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=Prediksi" class="{ 'active' if st.session_state.page == 'Prediksi' else '' }">Prediksi</a>
|
||||
<a href="?page=exploration" class="{ 'active' if st.session_state.page == 'exploration' else '' }">Dashboard</a>
|
||||
<a href="?page=report" class="{ 'active' if st.session_state.page == 'report' else '' }">Laporan</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
def save_prediction_to_db(employee_id, hasil_prediksi_klasifikasi, probabilitas_pred_klasifikasi, hasil_prediksi_regresi):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
cursor = conn.cursor()
|
||||
query = """
|
||||
INSERT INTO history_prediction (employee_id, hasil_prediksi_klasifikasi, probabilitas_pred_klasifikasi, hasil_prediksi_regresi)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(query, (employee_id, hasil_prediksi_klasifikasi, probabilitas_pred_klasifikasi, hasil_prediksi_regresi))
|
||||
conn.commit() # Pastikan perubahan disimpan
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat menyimpan ke database: {e}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def save_shap_to_db_with_features(employee_id, shap_dict):
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
shap_values_json = json.dumps(shap_dict)
|
||||
|
||||
# Query untuk menyimpan data ke database
|
||||
query = """
|
||||
INSERT INTO shap_pred_result (employee_id, shap_values)
|
||||
VALUES (%s, %s)
|
||||
"""
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(query, (employee_id, shap_values_json))
|
||||
conn.commit()
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat menyimpan SHAP values: {e}")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def show_prediction():
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
st.markdown(
|
||||
"""
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Halaman Prediksi
|
||||
</h3>
|
||||
<p style="font-family: 'Poppins', sans-serif;">
|
||||
Masukkan ID Karyawan untuk memulai prediksi.
|
||||
</p>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
employee_id = st.text_input("Masukkan ID Karyawan", placeholder="Contoh: EM12345")
|
||||
|
||||
# Tombol untuk memulai prediksi
|
||||
if st.button("Validasi dan Prediksi"):
|
||||
if not employee_id:
|
||||
st.error("Harap masukkan ID Karyawan terlebih dahulu.")
|
||||
return
|
||||
|
||||
# Ambil data karyawan dari database
|
||||
employee_data = get_employee_data_from_db(employee_id)
|
||||
if employee_data is None:
|
||||
st.error("ID Karyawan tidak ditemukan. Harap masukkan ID yang valid.")
|
||||
return
|
||||
|
||||
# Proses data karyawan
|
||||
df = pd.DataFrame([employee_data]) # Ubah dictionary menjadi DataFrame
|
||||
df = process_employee_data(df) # Proses data
|
||||
|
||||
# Kolom yang diharapkan oleh model
|
||||
expected_columns_class = class_model.feature_names_
|
||||
expected_columns_reg = reg_model.feature_names_
|
||||
|
||||
# Kolom kategori
|
||||
cat_feature = ['departemen', 'position', 'domisili', 'marriage_stat', 'job_satisfaction',
|
||||
'performance_rating', 'education', 'active_work_category', 'resign_risk_indicator', 'jenis_kelamin']
|
||||
|
||||
# Pastikan data uji memiliki kolom yang sesuai dengan model
|
||||
X_test_class = df[expected_columns_class]
|
||||
X_test_reg = df[expected_columns_reg]
|
||||
|
||||
# Konversi fitur kategori menjadi string
|
||||
for col in cat_feature:
|
||||
if col in X_test_class.columns:
|
||||
X_test_class[col] = X_test_class[col].astype(str)
|
||||
if col in X_test_reg.columns:
|
||||
X_test_reg[col] = X_test_reg[col].astype(str)
|
||||
|
||||
# Pool untuk data uji
|
||||
test_pool_class = Pool(data=X_test_class, cat_features=cat_feature)
|
||||
test_pool_reg = Pool(data=X_test_reg, cat_features=cat_feature)
|
||||
|
||||
# Prediksi
|
||||
classification_prob = class_model.predict_proba(test_pool_class)
|
||||
regression_result = reg_model.predict(test_pool_reg)
|
||||
|
||||
# Ambil hasil prediksi
|
||||
predicted_class = 1 if classification_prob[0][1] > 0.5 else 0
|
||||
hasil_prediksi_retensi = 'Tidak Retensi' if predicted_class == 1 else 'Retensi'
|
||||
probabilitas_pred_retensi = classification_prob[0][1] * 100 # Dalam persen
|
||||
hasil_prediksi_regresi = round(regression_result[0], 2)
|
||||
|
||||
warna_retensi = "green" if hasil_prediksi_retensi == "Retensi" else "red"
|
||||
|
||||
# Tampilkan hasil dalam kotak dengan warna
|
||||
st.markdown(
|
||||
f"""
|
||||
<div style="border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background-color: #FFFFFF;">
|
||||
<h4 style="color: {warna_retensi}; text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Prediksi Kemungkinan Retensi: {hasil_prediksi_retensi}
|
||||
</h4>
|
||||
<p style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
<b>Probabilitas Kemungkinan Retensi:</b> {classification_prob[0][0]:.2f}
|
||||
</p>
|
||||
<p style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
<b>Prediksi Durasi Kerja (bulan):</b> {hasil_prediksi_regresi} bulan
|
||||
</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Simpan hasil ke database
|
||||
save_prediction_to_db(employee_id, hasil_prediksi_retensi, probabilitas_pred_retensi, hasil_prediksi_regresi)
|
||||
|
||||
df_train = pd.read_csv(train_file_path)
|
||||
background_data = df_train.sample(n=min(len(df_train), 50), random_state=42)
|
||||
|
||||
# TreeExplainer untuk model klasifikasi
|
||||
explainer_class = shap.TreeExplainer(class_model, feature_perturbation="tree_path_dependent")
|
||||
shap_values_class = explainer_class.shap_values(X_test_class)
|
||||
|
||||
# Pastikan SHAP values valid
|
||||
if isinstance(shap_values_class, list) and len(shap_values_class) > 1:
|
||||
try:
|
||||
if predicted_class == 1:
|
||||
shap_values = shap_values_class[1][0] # Ambil nilai SHAP untuk class 1
|
||||
else:
|
||||
shap_values = shap_values_class[0][0] # Ambil nilai SHAP untuk class 0
|
||||
except IndexError:
|
||||
st.error("SHAP values list index out of range.")
|
||||
return
|
||||
elif not isinstance(shap_values_class, list):
|
||||
shap_values = shap_values_class[0] # Single-class output
|
||||
else:
|
||||
st.error("SHAP values tidak valid.")
|
||||
return
|
||||
|
||||
# Konversi SHAP values ke bentuk list
|
||||
shap_values_list = shap_values.flatten() # Rata array SHAP values menjadi 1D
|
||||
feature_names = list(X_test_class.columns) # Ambil semua nama fitur
|
||||
|
||||
# Validasi panjang SHAP values dan nama fitur
|
||||
if len(feature_names) != len(shap_values_list):
|
||||
st.error(
|
||||
f"Jumlah fitur ({len(feature_names)}) tidak sesuai dengan jumlah SHAP values ({len(shap_values_list)})."
|
||||
)
|
||||
return # Stop eksekusi jika tidak sesuai
|
||||
|
||||
shap_dict = {feature: shap_values_list[i] for i, feature in enumerate(feature_names)}
|
||||
|
||||
save_shap_to_db_with_features(employee_id, shap_dict)
|
||||
|
||||
plot_placeholder = st.empty()
|
||||
|
||||
# Modify the generate_shap_plot function
|
||||
def generate_shap_plot(X_test_class, explainer_class):
|
||||
plt.close('all')
|
||||
|
||||
try:
|
||||
# Generate new SHAP explanation
|
||||
shap_explanation = explainer_class(X_test_class.iloc[0:1])
|
||||
|
||||
# Create figure
|
||||
plt.figure(figsize=(4, 2), dpi=100)
|
||||
|
||||
# Handle different SHAP value structures
|
||||
if isinstance(shap_explanation, list):
|
||||
# For multi-class output
|
||||
if predicted_class == 1 and len(shap_explanation) > 1:
|
||||
shap.plots.waterfall(shap_explanation[1][0])
|
||||
else:
|
||||
shap.plots.waterfall(shap_explanation[0][0])
|
||||
else:
|
||||
# For single-class output
|
||||
shap.plots.waterfall(shap_explanation[0])
|
||||
|
||||
# Save and display plot
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf, format='png', bbox_inches="tight", dpi=100)
|
||||
buf.seek(0)
|
||||
cols = st.columns(2) # Bagi halaman menjadi 4 kolom
|
||||
with cols[0]: # Grafik SHAP ditempatkan di kolom pertama
|
||||
st.image(buf, caption="SHAP Waterfall Plot", use_container_width=True)
|
||||
|
||||
plt.close()
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error generating SHAP plot: {str(e)}")
|
||||
plt.close()
|
||||
|
||||
generate_shap_plot(X_test_class, explainer_class)
|
||||
|
||||
top_factors = sorted(shap_dict.items(), key=lambda x: abs(x[1]), reverse=True)[:5]
|
||||
|
||||
# Bangun kesimpulan dinamis
|
||||
summary = " dan ".join(
|
||||
[f"<b>{factor}</b> dengan kontribusi <b>{'+' if value > 0 else ''}{value:.2f}</b>" for factor, value in top_factors]
|
||||
)
|
||||
|
||||
# Kesimpulan Dinamis
|
||||
st.markdown(
|
||||
f"""
|
||||
<div style="text-align: justify; font-family: 'Poppins', sans-serif;">
|
||||
Grafik ini menunjukkan bagaimana hasil prediksi dihitung berdasarkan beberapa faktor utama.
|
||||
Faktor-faktor yang paling memengaruhi hasil prediksi adalah {summary}.
|
||||
Faktor-faktor ini memberikan kontribusi signifikan terhadap hasil akhir prediksi,
|
||||
baik dalam meningkatkan maupun menurunkan probabilitas retensi karyawan.
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Jalankan fungsi show_prediction
|
||||
if __name__ == "__main__":
|
||||
show_prediction()
|
295
pages/report.py
Normal file
@ -0,0 +1,295 @@
|
||||
import streamlit as st
|
||||
import os
|
||||
import mysql.connector
|
||||
import pandas as pd
|
||||
import json
|
||||
|
||||
# Fungsi untuk mendapatkan gambar sebagai base64
|
||||
def get_image_as_base64(image_path):
|
||||
import base64
|
||||
with open(image_path, "rb") as img_file:
|
||||
return base64.b64encode(img_file.read()).decode("utf-8")
|
||||
|
||||
# Fungsi untuk koneksi ke database
|
||||
def connect_to_db():
|
||||
try:
|
||||
conn = mysql.connector.connect(
|
||||
host=st.secrets["mysql"]["host"],
|
||||
user=st.secrets["mysql"]["user"],
|
||||
password=st.secrets["mysql"]["password"],
|
||||
database=st.secrets["mysql"]["dbname"],
|
||||
port=st.secrets["mysql"]["port"]
|
||||
)
|
||||
return conn
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Koneksi ke database gagal: {e}")
|
||||
return None
|
||||
|
||||
# Fungsi untuk mengambil data dari tabel history_prediction
|
||||
def get_all_predictions():
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = """SELECT employee_id as ID_Karyawan, hasil_prediksi_klasifikasi as Hasil_Prediksi_Retensi,
|
||||
hasil_prediksi_regresi as Hasil_Prediksi_Lama_Kerja, waktu_prediksi as Waktu_Prediksi
|
||||
FROM history_prediction"""
|
||||
df = pd.read_sql(query, conn) # Menggunakan Pandas untuk membaca data
|
||||
return df
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengambil data dari database: {e}")
|
||||
return pd.DataFrame() # Kembalikan DataFrame kosong jika terjadi error
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# Fungsi untuk mengambil data dari tabel shap_pred_result
|
||||
def get_shap_top_features():
|
||||
conn = connect_to_db()
|
||||
if conn:
|
||||
try:
|
||||
query = "SELECT employee_id as ID_Karyawan, shap_values FROM shap_pred_result"
|
||||
df = pd.read_sql(query, conn)
|
||||
|
||||
# Ekstraksi dan format ulang shap_values
|
||||
result = []
|
||||
for _, row in df.iterrows():
|
||||
employee_id = row['ID_Karyawan']
|
||||
shap_values = json.loads(row['shap_values'])
|
||||
|
||||
# Pastikan nilai SHAP berupa angka tunggal, jika list maka ambil rata-rata
|
||||
normalized_shap_values = {
|
||||
feature: (sum(value) / len(value) if isinstance(value, list) else value)
|
||||
for feature, value in shap_values.items()
|
||||
}
|
||||
|
||||
# Ambil 5 fitur dengan SHAP value tertinggi (absolut)
|
||||
top_features = sorted(
|
||||
normalized_shap_values.items(),
|
||||
key=lambda x: abs(x[1]),
|
||||
reverse=True
|
||||
)[:5]
|
||||
|
||||
# Buat format tabel baru
|
||||
formatted_row = {
|
||||
"ID_Karyawan": employee_id
|
||||
}
|
||||
for i, (feature, value) in enumerate(top_features, start=1):
|
||||
formatted_row[f"Nama_Fitur_{i}"] = feature
|
||||
formatted_row[f"Besar_Value_{i}"] = value
|
||||
result.append(formatted_row)
|
||||
|
||||
# Konversi ke DataFrame
|
||||
formatted_df = pd.DataFrame(result)
|
||||
return formatted_df
|
||||
except mysql.connector.Error as e:
|
||||
st.error(f"Terjadi kesalahan saat mengambil data: {e}")
|
||||
return pd.DataFrame()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
# Fungsi untuk menampilkan navbar
|
||||
def navbar():
|
||||
current_page = st.session_state.get("page", "Home")
|
||||
logo_path = os.path.join(os.path.dirname(__file__), "../asset/logo.png")
|
||||
|
||||
# Cek status login
|
||||
if 'logged_in' in st.session_state and st.session_state['logged_in']:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login&logout=true" # Tambahkan parameter logout
|
||||
else:
|
||||
login_button_text = "Logout"
|
||||
login_button_link = "?page=Login"
|
||||
|
||||
st.markdown(
|
||||
f"""
|
||||
<style>
|
||||
.navbar {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 20px;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
margin-top: 20px; /* Hilangkan jarak atas */
|
||||
background-color: #D0EEFF; /* Background navbar */
|
||||
border-radius: 15px; /* Membulatkan sudut navbar */
|
||||
}}
|
||||
.navbar .logo {{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}}
|
||||
.navbar .logo img {{
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
}}
|
||||
.navbar .nav-links {{
|
||||
display: flex;
|
||||
gap: 60px;
|
||||
}}
|
||||
.navbar .nav-links a {{
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.navbar .nav-links a:hover {{
|
||||
color: royalblue;
|
||||
}}
|
||||
.navbar .nav-links a.active {{
|
||||
color: #264CBE; /* Warna saat aktif */
|
||||
text-decoration: underline; /* Garis bawah saat aktif */
|
||||
}}
|
||||
.navbar .login-button {{
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}}
|
||||
.navbar .login-button:hover {{
|
||||
background-color: white;
|
||||
color: #264CBE;
|
||||
}}
|
||||
</style>
|
||||
<div class="navbar">
|
||||
<div class="logo">
|
||||
<img src="data:image/png;base64,{get_image_as_base64(logo_path)}" alt="Logo">
|
||||
</div>
|
||||
<div class="nav-links">
|
||||
<a href="?page=Prediksi" class="{ 'active' if st.session_state.page == 'Prediksi' else '' }">Prediksi</a>
|
||||
<a href="?page=exploration" class="{ 'active' if st.session_state.page == 'exploration' else '' }">Dashboard</a>
|
||||
<a href="?page=report" class="{ 'active' if st.session_state.page == 'report' else '' }">Laporan</a>
|
||||
</div>
|
||||
<a class="login-button" href="{login_button_link}">{login_button_text}</a>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True,
|
||||
)
|
||||
|
||||
def show_report():
|
||||
# Tampilkan navbar
|
||||
navbar()
|
||||
|
||||
st.markdown("""
|
||||
<style>
|
||||
.stDownloadButton > button {
|
||||
background-color: #264CBE;
|
||||
color: white;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.stDownloadButton > button:hover {
|
||||
background-color: #ffffff;
|
||||
color: #264CBE;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
width: 100%;
|
||||
background-color: #D0EEFF;
|
||||
padding: 20px !important;
|
||||
text-align: center;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
border-radius: 10px;
|
||||
margin-top: 50px !important;
|
||||
}
|
||||
|
||||
.footer p {
|
||||
margin: 5px 0;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Konten halaman Laporan
|
||||
st.markdown(
|
||||
"""
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
||||
<h3 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Halaman Laporan
|
||||
</h3>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Menu dropdown
|
||||
menu_option = st.selectbox(
|
||||
"Pilih data yang ingin ditampilkan:",
|
||||
["History Prediksi", "History SHAP Values"]
|
||||
)
|
||||
|
||||
if menu_option == "History Prediksi":
|
||||
st.markdown(
|
||||
"""
|
||||
<h5 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Tabel Histori Prediksi
|
||||
</h5>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
# Ambil data dari tabel history_prediction
|
||||
df = get_all_predictions()
|
||||
|
||||
if not df.empty:
|
||||
# Tampilkan data dalam bentuk tabel
|
||||
st.dataframe(df)
|
||||
|
||||
# Tombol untuk mendownload CSV
|
||||
csv = df.to_csv(index=False) # Konversi DataFrame ke CSV tanpa indeks
|
||||
st.download_button(
|
||||
label="Download Tabel sebagai CSV",
|
||||
data=csv,
|
||||
file_name="history_prediction.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.write("Tidak ada data yang tersedia di tabel history_prediction.")
|
||||
|
||||
elif menu_option == "History SHAP Values":
|
||||
st.markdown(
|
||||
"""
|
||||
<h5 style="text-align: center; font-family: 'Poppins', sans-serif;">
|
||||
Tabel Histori SHAP Values
|
||||
</h5>
|
||||
""", unsafe_allow_html=True
|
||||
)
|
||||
# Ambil data dari tabel shap_pred_result
|
||||
df = get_shap_top_features()
|
||||
|
||||
if not df.empty:
|
||||
# Tampilkan data dalam bentuk tabel
|
||||
st.dataframe(df)
|
||||
|
||||
# Tombol untuk mendownload CSV
|
||||
csv = df.to_csv(index=False) # Konversi DataFrame ke CSV tanpa indeks
|
||||
st.download_button(
|
||||
label="Download Tabel sebagai CSV",
|
||||
data=csv,
|
||||
file_name="shap_pred_result.csv",
|
||||
mime="text/csv",
|
||||
)
|
||||
else:
|
||||
st.write("Tidak ada data yang tersedia di tabel shap_pred_result.")
|
||||
|
||||
# Footer
|
||||
st.markdown(
|
||||
"""
|
||||
<div class="footer">
|
||||
<p><strong>2025 © Jesselyn Mu</strong></p>
|
||||
<p>Untuk informasi lebih lanjut, dapat mengirim email ke mujesselyn@gmail.com</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
# Jalankan fungsi show_report
|
||||
if __name__ == "__main__":
|
||||
show_report()
|
BIN
regression_model.sav
Normal file
95
requirements.txt
Normal file
@ -0,0 +1,95 @@
|
||||
altair==5.5.0
|
||||
asttokens==3.0.0
|
||||
attrs==24.3.0
|
||||
blinker==1.9.0
|
||||
cachetools==5.5.1
|
||||
catboost==1.2.7
|
||||
certifi==2024.12.14
|
||||
charset-normalizer==3.4.1
|
||||
click==8.1.8
|
||||
cloudpickle==3.1.1
|
||||
colorama==0.4.6
|
||||
comm==0.2.2
|
||||
contourpy==1.3.0
|
||||
cycler==0.12.1
|
||||
debugpy==1.8.12
|
||||
decorator==5.1.1
|
||||
exceptiongroup==1.2.2
|
||||
executing==2.2.0
|
||||
fonttools==4.55.6
|
||||
gitdb==4.0.12
|
||||
GitPython==3.1.44
|
||||
graphviz==0.20.3
|
||||
idna==3.10
|
||||
importlib-metadata==8.6.1
|
||||
importlib-resources==6.5.2
|
||||
ipykernel==6.29.5
|
||||
ipython==8.18.1
|
||||
jedi==0.19.2
|
||||
jinja2==3.1.5
|
||||
joblib==1.4.2
|
||||
jsonschema==4.23.0
|
||||
jsonschema-specifications==2024.10.1
|
||||
jupyter-client==8.6.3
|
||||
jupyter-core==5.7.2
|
||||
kiwisolver==1.4.7
|
||||
llvmlite==0.43.0
|
||||
markdown-it-py==3.0.0
|
||||
MarkupSafe==3.0.2
|
||||
matplotlib==3.9.4
|
||||
matplotlib-inline==0.1.7
|
||||
mdurl==0.1.2
|
||||
mysql-connector-python==9.2.0
|
||||
narwhals==1.23.0
|
||||
nest-asyncio==1.6.0
|
||||
numba==0.60.0
|
||||
numpy==1.26.4
|
||||
packaging==24.2
|
||||
pandas==2.2.3
|
||||
parso==0.8.4
|
||||
patsy==1.0.1
|
||||
pillow==10.4.0
|
||||
platformdirs==4.3.6
|
||||
plotly==5.24.1
|
||||
plotly-express==0.4.1
|
||||
prompt-toolkit==3.0.50
|
||||
protobuf==5.29.3
|
||||
psutil==6.1.1
|
||||
pure-eval==0.2.3
|
||||
pyarrow==19.0.0
|
||||
pydeck==0.9.1
|
||||
pygments==2.19.1
|
||||
pyparsing==3.2.1
|
||||
python-dateutil==2.9.0.post0
|
||||
pytz==2024.2
|
||||
pywin32==308
|
||||
pyzmq==26.2.0
|
||||
referencing==0.36.1
|
||||
requests==2.32.3
|
||||
rich==13.9.4
|
||||
rpds-py==0.22.3
|
||||
scikit-learn==1.6.1
|
||||
scipy==1.13.1
|
||||
seaborn==0.13.2
|
||||
shap==0.46.0
|
||||
six==1.17.0
|
||||
slicer==0.0.8
|
||||
smmap==5.0.2
|
||||
st-theme==1.2.3
|
||||
stack-data==0.6.3
|
||||
statsmodels==0.14.4
|
||||
streamlit==1.41.0
|
||||
streamlit-navigation-bar==3.3.0
|
||||
streamlit-option-menu==0.4.0
|
||||
tenacity==8.5.0
|
||||
threadpoolctl==3.5.0
|
||||
toml==0.10.2
|
||||
tornado==6.4.2
|
||||
tqdm==4.67.1
|
||||
traitlets==5.14.3
|
||||
typing-extensions==4.12.2
|
||||
tzdata==2025.1
|
||||
urllib3==2.3.0
|
||||
watchdog==4.0.2
|
||||
wcwidth==0.2.13
|
||||
zipp==3.21.0
|