first commit

This commit is contained in:
Jesselyn Mu
2025-01-28 10:05:01 +07:00
commit c6f1bb970b
40 changed files with 11294 additions and 0 deletions

6
.streamlit/secrets.toml Normal file
View File

@ -0,0 +1,6 @@
[mysql]
host = "localhost"
port = 3306
user = "root"
password = ""
dbname = "employee_attrition_db"

261
App.py Normal file
View 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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.2 MiB

8093
X_train.csv Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
asset/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
asset/main_img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
asset/news1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
asset/news2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
asset/news3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

BIN
asset/news4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
asset/news5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
asset/news6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
asset/news7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
asset/news8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
asset/sitemap.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
clasification_model.sav Normal file

Binary file not shown.

218
login.py Normal file
View 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
View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

363
pages/exploration.py Normal file
View 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
View 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
View 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
View 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()

View 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
View 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
View 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
View 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

Binary file not shown.

95
requirements.txt Normal file
View 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