Isi Menu "Setting"

  Isi Menu "Setting"

(Konten Sub Menu " Ubah  Profil Pengguna ")


1. Sub Menu       "Ubah Profil Pengguna"

Sekarang kita masuk ke  Menu " Setting kita lanjut membuat konten Sub Menu  "Ubah  Profil Pengguna".

Sip 👍 kita fokus ke Menu Setting → Sub Menu "Ubah Profil Pengguna" .

Biasanya, Ubah Profil Pengguna mencakup hal-hal seperti:

  • Nama Pengguna (misalnya: "SR Pakpahan")
  • Email (opsional)
  • Nomor HP (opsional)
  • Preferensi / Catatan (opsional)

Di aplikasi tkinter , sub menu ini bisa dibuat sebagai frame khusus yang menampilkan form entry, dengan tombol Simpan dan Kembali .

Saya buatkan contoh kode sederhana:

import tkinter as tk
from tkinter import messagebox

# =============================
# Sub Menu: Ubah Profil Pengguna
# =============================
def show_sub_menu_ubah_profil(root_frame):
    for w in root_frame.winfo_children():
        w.destroy()

    tk.Label(root_frame, text="Ubah Profil Pengguna",
             font=("Arial", 13, "bold")).pack(pady=10)

    frame_form = tk.LabelFrame(root_frame, text="Profil", padx=10, pady=10)
    frame_form.pack(fill="x", padx=15, pady=8)

    # Input field
    tk.Label(frame_form, text="Nama Pengguna:").grid(row=0, column=0, sticky="w", pady=3)
    nama_entry = tk.Entry(frame_form, width=40)
    nama_entry.grid(row=0, column=1, pady=3)

    tk.Label(frame_form, text="Email:").grid(row=1, column=0, sticky="w", pady=3)
    email_entry = tk.Entry(frame_form, width=40)
    email_entry.grid(row=1, column=1, pady=3)

    tk.Label(frame_form, text="Nomor HP:").grid(row=2, column=0, sticky="w", pady=3)
    hp_entry = tk.Entry(frame_form, width=40)
    hp_entry.grid(row=2, column=1, pady=3)

    tk.Label(frame_form, text="Catatan:").grid(row=3, column=0, sticky="nw", pady=3)
    catatan_text = tk.Text(frame_form, width=30, height=4, wrap="word")
    catatan_text.grid(row=3, column=1, pady=3)

    # Fungsi simpan profil
    def simpan_profil():
        nama = nama_entry.get().strip()
        email = email_entry.get().strip()
        hp = hp_entry.get().strip()
        catatan = catatan_text.get("1.0", "end").strip()

        if not nama:
            messagebox.showwarning("Kosong", "Nama Pengguna harus diisi!")
            return

        # 👉 nanti bisa disimpan ke database / file config
        messagebox.showinfo("Tersimpan",
                            f"Profil berhasil disimpan:\n\n"
                            f"Nama: {nama}\nEmail: {email}\nHP: {hp}\nCatatan: {catatan}")

    # Tombol aksi
    row_btn = tk.Frame(root_frame)
    row_btn.pack(pady=10)

    tk.Button(row_btn, text="💾 Simpan", bg="#388E3C", fg="white",
              width=12, command=simpan_profil).pack(side="left", padx=5)

    def kembali():
        for w in root_frame.winfo_children():
            w.destroy()
        from menu_setting import show_menu_setting
        show_menu_setting(root_frame)

    tk.Button(row_btn, text="↩ Kembali", bg="#757575", fg="white",
              width=12, command=kembali).pack(side="left", padx=5)

⚡ Flow kerja:

  1. Pengguna buka Menu Pengaturan → Ubah Profil Pengguna .
  2. Muncul form input Nama, Email, HP, Catatan.
  3. Isi pengguna, lalu klik Simpan → muncul konfirmasi (nanti bisa diarahkan penyimpanan ke database/file).
  4. Tombol Kembali mengembalikan ke menu utama Setting.

Mau saya langsung menghubungkan penyimpanan profil pengguna ini ke database SQLite ( DATAMUSIK.db) atau cukup dulu pakai popup info (sementara) ?


Mau kamu langsung menghubungkan penyimpanan profil pengguna ini ke database SQLite (data_profil_pengguna.db) di folder /storage/emulated/0/SmartNutriChefAI/Database/

Saya sudah membuat kode isi file menu_pengaturan.py berikut ini:

Menu Pengaturan

Menu Pengaturan

impor tkinter sebagai tk
dari tkinter impor ttk

dari Setting.sub_menu_ubah_profil_pengguna import show_sub_menu_ubah_profil

def show_menu_pengaturan(root_frame):
from menu_utama import tampilkan_halaman_utama # import lokal agar aman

# Bersihkan frame  
for widget in root_frame.winfo_children():  
    widget.destroy()  
      
# Judul  
label_title = tk.Label(root_frame, text="Pengaturan Aplikasi", font=("Arial", 16, "bold"), bg="light grey")  
label_title.pack(pady=10)  

    # Submenu tombol  
submenus = [	    	      
    ("Ubah Profil Pengguna", lambda: show_sub_menu_ubah_profil(root_frame)),  
  
    "Setelan Preferensi Nutrisi",  
    "Bahasa Aplikasi",  
    "Tema Tampilan"  
]  

for submenu in submenus:  
    btn = tk.Button(root_frame, text=submenu, font=("Arial", 12), width=35, height=2, bg="#fce4ec", fg="dark green")  
    btn.pack(pady=5)  

# Tombol kembali  
tombol_kembali = tk.Button(  
    root_frame,  
    text="Kembali ke Menu Utama",  
    font=("Arial", 12),  
    bg="#d9534f",  
    fg="white",  
    command=lambda: tampilkan_halaman_utama(root_frame)  
)  
tombol_kembali.pack(pady=30)

Mantap 👍
Kalau mau langsung menyimpan profil pengguna ke SQLite , kita buat file sub_menu_ubah_profil_pengguna.pydengan koneksi ke database:

📂 Struktur folder:

/storage/emulated/0/SmartNutriChefAI/
    ├── Database/
    │     └── data_profil_pengguna.db
    ├── Setting/
    │     └── sub_menu_ubah_profil_pengguna.py
    └── menu_pengaturan.py

Isi fileSetting/sub_menu_ubah_profil_pengguna.py

import os
import sqlite3
import tkinter as tk
from tkinter import messagebox

# Lokasi database
DB_FOLDER = "/storage/emulated/0/SmartNutriChefAI/Database"
DB_PATH = os.path.join(DB_FOLDER, "data_profil_pengguna.db")


# ======== INIT DB ========
def init_db():
    if not os.path.exists(DB_FOLDER):
        os.makedirs(DB_FOLDER, exist_ok=True)

    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""
        CREATE TABLE IF NOT EXISTS profil_pengguna (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nama TEXT NOT NULL,
            email TEXT,
            hp TEXT,
            catatan TEXT
        )
    """)
    conn.commit()
    conn.close()


# ======== SIMPAN PROFIL ========
def simpan_profil_ke_db(nama, email, hp, catatan):
    init_db()
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    # Jika sudah ada data, update (hanya 1 profil disimpan)
    c.execute("SELECT id FROM profil_pengguna LIMIT 1")
    row = c.fetchone()
    if row:
        c.execute("""
            UPDATE profil_pengguna 
            SET nama=?, email=?, hp=?, catatan=? 
            WHERE id=?
        """, (nama, email, hp, catatan, row[0]))
    else:
        c.execute("""
            INSERT INTO profil_pengguna (nama, email, hp, catatan)
            VALUES (?, ?, ?, ?)
        """, (nama, email, hp, catatan))

    conn.commit()
    conn.close()


# ======== AMBIL PROFIL ========
def ambil_profil():
    init_db()
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("SELECT nama, email, hp, catatan FROM profil_pengguna LIMIT 1")
    row = c.fetchone()
    conn.close()
    return row if row else ("", "", "", "")


# ======== SUB MENU UI ========
def show_sub_menu_ubah_profil(root_frame):
    for w in root_frame.winfo_children():
        w.destroy()

    tk.Label(root_frame, text="Ubah Profil Pengguna",
             font=("Arial", 13, "bold")).pack(pady=10)

    frame_form = tk.LabelFrame(root_frame, text="Profil", padx=10, pady=10)
    frame_form.pack(fill="x", padx=15, pady=8)

    # Input field
    tk.Label(frame_form, text="Nama Pengguna:").grid(row=0, column=0, sticky="w", pady=3)
    nama_entry = tk.Entry(frame_form, width=40)
    nama_entry.grid(row=0, column=1, pady=3)

    tk.Label(frame_form, text="Email:").grid(row=1, column=0, sticky="w", pady=3)
    email_entry = tk.Entry(frame_form, width=40)
    email_entry.grid(row=1, column=1, pady=3)

    tk.Label(frame_form, text="Nomor HP:").grid(row=2, column=0, sticky="w", pady=3)
    hp_entry = tk.Entry(frame_form, width=40)
    hp_entry.grid(row=2, column=1, pady=3)

    tk.Label(frame_form, text="Catatan:").grid(row=3, column=0, sticky="nw", pady=3)
    catatan_text = tk.Text(frame_form, width=30, height=4, wrap="word")
    catatan_text.grid(row=3, column=1, pady=3)

    # Isi field dengan data lama kalau ada
    nama, email, hp, catatan = ambil_profil()
    nama_entry.insert(0, nama)
    email_entry.insert(0, email)
    hp_entry.insert(0, hp)
    catatan_text.insert("1.0", catatan)

    # Fungsi simpan
    def simpan_profil():
        nama_val = nama_entry.get().strip()
        email_val = email_entry.get().strip()
        hp_val = hp_entry.get().strip()
        catatan_val = catatan_text.get("1.0", "end").strip()

        if not nama_val:
            messagebox.showwarning("Kosong", "Nama Pengguna harus diisi!")
            return

        simpan_profil_ke_db(nama_val, email_val, hp_val, catatan_val)
        messagebox.showinfo("Tersimpan", "Profil berhasil disimpan!")

    # Tombol aksi
    row_btn = tk.Frame(root_frame)
    row_btn.pack(pady=10)

    tk.Button(row_btn, text="💾 Simpan", bg="#388E3C", fg="white",
              width=12, command=simpan_profil).pack(side="left", padx=5)

    def kembali():
        for w in root_frame.winfo_children():
            w.destroy()
        from menu_pengaturan import show_menu_pengaturan
        show_menu_pengaturan(root_frame)

    tk.Button(row_btn, text="↩ Kembali", bg="#757575", fg="white",
              width=12, command=kembali).pack(side="left", padx=5)

📌 Dengan kode ini:

  • Database otomatis dibuat di/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db
  • Pengguna bisa isi Nama, Email, HP, Catatan → klik Simpan → data tersimpan di DB.
  • Kalau buka lagi Ubah Profil Pengguna , kolom otomatis terisi dengan data lama.

Mau saya tambahkan juga fitur tampilkan profil pengguna di menu Setting (supaya user bisa lihat profil tersimpan sebelum mengubah), atau cukup menu Ubah Profil saja? 

File sub_menu_ubah_profil_pengguna.py saya simpan di folder /storage/emulated/0/SmartNutriChefAI/Setting/
Lalu kamu bantu perbaiki kode isi file menu_pengatutan.py

Oke 👍, saya bantu rapikan kode menu_pengaturan.py supaya tombol "Ubah Profil Pengguna" benar-benar memanggil show_sub_menu_ubah_profil(root_frame) dari file sub_menu_ubah_profil_pengguna.py.

Berikut perbaikan kode:

# menu_pengaturan.py
import tkinter as tk
from Setting.sub_menu_ubah_profil_pengguna import show_sub_menu_ubah_profil


def show_menu_pengaturan(root_frame):
    from menu_utama import tampilkan_halaman_utama  # Import lokal agar aman

    # Bersihkan frame sebelum tampilkan isi baru
    for widget in root_frame.winfo_children():
        widget.destroy()

    # Judul
    label_title = tk.Label(
        root_frame,
        text="Pengaturan Aplikasi",
        font=("Arial", 16, "bold"),
        bg="light grey"
    )
    label_title.pack(pady=10)

    # Submenu daftar
    submenus = [
        ("Ubah Profil Pengguna", lambda: show_sub_menu_ubah_profil(root_frame)),
        ("Setelan Preferensi Nutrisi", None),
        ("Bahasa Aplikasi", None),
        ("Tema Tampilan", None),
    ]

    for submenu in submenus:
        if isinstance(submenu, tuple):
            text, command = submenu
            btn = tk.Button(
                root_frame,
                text=text,
                font=("Arial", 12),
                width=35,
                height=2,
                bg="#fce4ec",
                fg="dark green",
                command=command
            )
        else:
            # Jika hanya string biasa
            btn = tk.Button(
                root_frame,
                text=submenu,
                font=("Arial", 12),
                width=35,
                height=2,
                bg="#fce4ec",
                fg="dark green"
            )
        btn.pack(pady=5)

    # Tombol kembali
    tombol_kembali = tk.Button(
        root_frame,
        text="Kembali ke Menu Utama",
        font=("Arial", 12),
        bg="#d9534f",
        fg="white",
        command=lambda: tampilkan_halaman_utama(root_frame)
    )
    tombol_kembali.pack(pady=30)

🔹 Perbaikan utama:

  1. Tombol "Ubah Profil Pengguna" sekarang langsung memanggil show_sub_menu_ubah_profil(root_frame).
  2. Submenu lain masih placeholder (None) supaya nanti bisa diisi.
  3. Struktur submenus dibedakan antara tuple (teks, command) dan string biasa.

Mau saya lanjut bikinkan isi sub_menu_ubah_profil_pengguna.py yang sudah terhubung SQLite data_profil_pengguna.db (create table + simpan + update) biar langsung jalan?


Data profil pengguna belum dapat tersimpan. Mau kamu lanjut perbaiki isi sub_menu_ubah_profil_pengguna.py agar terhubung ke SQLite data_profil_pengguna.db (create table + simpan + update) biar langsung jalan.
Kode:
import os
import sqlite3
import tkinter as tk
from tkinter import messagebox

Lokasi database

DB_FOLDER = "/storage/emulated/0/SmartNutriChefAI/Database"
DB_PATH = os.path.join(DB_FOLDER, "data_profil_pengguna.db")

======== INIT DB ========

def init_db():
if not os.path.exists(DB_FOLDER):
os.makedirs(DB_FOLDER, exist_ok=True)

conn = sqlite3.connect(DB_PATH)  
c = conn.cursor()  
c.execute("""  
    CREATE TABLE IF NOT EXISTS profil_pengguna (  
        id INTEGER PRIMARY KEY AUTOINCREMENT,  
        nama TEXT NOT NULL,  
        email TEXT,  
        hp TEXT,  
        catatan TEXT  
    )  
""")  
conn.commit()  
conn.close()  

======== SIMPAN PROFIL ========

def simpan_profil_ke_db(nama, email, hp, catatan):
init_db()
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()

# Jika sudah ada data, update (hanya 1 profil disimpan)  
c.execute("SELECT id FROM profil_pengguna LIMIT 1")  
row = c.fetchone()  
if row:  
    c.execute("""  
        UPDATE profil_pengguna   
        SET nama=?, email=?, hp=?, catatan=?   
        WHERE id=?  
    """, (nama, email, hp, catatan, row[0]))  
else:  
    c.execute("""  
        INSERT INTO profil_pengguna (nama, email, hp, catatan)  
        VALUES (?, ?, ?, ?)  
    """, (nama, email, hp, catatan))  

conn.commit()  
conn.close()  

======== AMBIL PROFIL ========

def ambil_profil():
init_db()
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("SELECT nama, email, hp, catatan FROM profil_pengguna LIMIT 1")
row = c.fetchone()
conn.close()
return row if row else ("", "", "", "")

======== SUB MENU UI ========

def show_sub_menu_ubah_profil(root_frame):
for w in root_frame.winfo_children():
w.destroy()

tk.Label(root_frame, text="Ubah Profil Pengguna",  
         font=("Arial", 15, "bold")).pack(pady=10)  

frame_form = tk.LabelFrame(root_frame, text="Profil", fg= "tomato", font=("Arial", 10, "bold"), padx=10, pady=10)  
frame_form.pack(fill="x", padx=15, pady=8)  

# Input field  
tk.Label(frame_form, text="Nama Pengguna:").grid(row=0, column=0, sticky="w", pady=3)  
nama_entry = tk.Entry(frame_form, width=40)  
nama_entry.grid(row=0, column=1, pady=3)  

tk.Label(frame_form, text="Email:").grid(row=1, column=0, sticky="w", pady=3)  
email_entry = tk.Entry(frame_form, width=40)  
email_entry.grid(row=1, column=1, pady=3)  

tk.Label(frame_form, text="Nomor HP:").grid(row=2, column=0, sticky="w", pady=3)  
hp_entry = tk.Entry(frame_form, width=40)  
hp_entry.grid(row=2, column=1, pady=3)  

tk.Label(frame_form, text="Catatan:").grid(row=3, column=0, sticky="nw", pady=3)  
catatan_text = tk.Text(frame_form, width=30, height=14, wrap="word")  
catatan_text.grid(row=3, column=1, pady=3, sticky= "w")  

# Isi field dengan data lama kalau ada  
nama, email, hp, catatan = ambil_profil()  
nama_entry.insert(0, nama)  
email_entry.insert(0, email)  
hp_entry.insert(0, hp)  
catatan_text.insert("1.0", catatan)  

# Fungsi simpan  
def simpan_profil():  
    nama_val = nama_entry.get().strip()  
    email_val = email_entry.get().strip()  
    hp_val = hp_entry.get().strip()  
    catatan_val = catatan_text.get("1.0", "end").strip()  

    if not nama_val:  
        messagebox.showwarning("Kosong", "Nama Pengguna harus diisi!")  
        return  

    simpan_profil_ke_db(nama_val, email_val, hp_val, catatan_val)  
    messagebox.showinfo("Tersimpan", "Profil berhasil disimpan!")  

# Tombol aksi  
row_btn = tk.Frame(root_frame)  
row_btn.pack(pady=10)  

tk.Button(row_btn, text="Simpan", bg="#388E3C", fg="white",  
          width=12, command=simpan_profil).pack(side="left", padx=5)  

def kembali():  
    for w in root_frame.winfo_children():  
        w.destroy()  
    from menu_pengaturan import show_menu_pengaturan  
    show_menu_pengaturan(root_frame)  

tk.Button(row_btn, text="↩ Kembali", bg="#757575", fg="white",  
          width=12, command=kembali).pack(side="left", padx=5)

Oke 👍 saya lihat kode sub_menu_ubah_profil_pengguna.py kamu sudah bagus, hanya perlu beberapa penyempurnaan supaya:

  1. Database folder benar-benar dibuat kalau belum ada.
  2. Koneksi SQLite ditutup dengan aman (pakai with sqlite3.connect).
  3. Pengisian data awal ke Entry dan Text tidak error walau database masih kosong.

Berikut perbaikan:

import os
import sqlite3
import tkinter as tk
from tkinter import messagebox

# Lokasi database
DB_FOLDER = "/storage/emulated/0/SmartNutriChefAI/Database"
DB_PATH = os.path.join(DB_FOLDER, "data_profil_pengguna.db")


# ======== INIT DB ========
def init_db():
    os.makedirs(DB_FOLDER, exist_ok=True)  # pastikan folder ada
    with sqlite3.connect(DB_PATH) as conn:
        c = conn.cursor()
        c.execute("""
            CREATE TABLE IF NOT EXISTS profil_pengguna (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                nama TEXT NOT NULL,
                email TEXT,
                hp TEXT,
                catatan TEXT
            )
        """)
        conn.commit()


# ======== SIMPAN PROFIL ========
def simpan_profil_ke_db(nama, email, hp, catatan):
    init_db()
    with sqlite3.connect(DB_PATH) as conn:
        c = conn.cursor()
        c.execute("SELECT id FROM profil_pengguna LIMIT 1")
        row = c.fetchone()
        if row:
            c.execute("""
                UPDATE profil_pengguna 
                SET nama=?, email=?, hp=?, catatan=? 
                WHERE id=?
            """, (nama, email, hp, catatan, row[0]))
        else:
            c.execute("""
                INSERT INTO profil_pengguna (nama, email, hp, catatan)
                VALUES (?, ?, ?, ?)
            """, (nama, email, hp, catatan))
        conn.commit()


# ======== AMBIL PROFIL ========
def ambil_profil():
    init_db()
    with sqlite3.connect(DB_PATH) as conn:
        c = conn.cursor()
        c.execute("SELECT nama, email, hp, catatan FROM profil_pengguna LIMIT 1")
        row = c.fetchone()
    return row if row else ("", "", "", "")


# ======== SUB MENU UI ========
def show_sub_menu_ubah_profil(root_frame):
    for w in root_frame.winfo_children():
        w.destroy()

    tk.Label(root_frame, text="Ubah Profil Pengguna",
             font=("Arial", 15, "bold")).pack(pady=10)

    frame_form = tk.LabelFrame(
        root_frame, text="Profil",
        fg="tomato", font=("Arial", 10, "bold"),
        padx=10, pady=10
    )
    frame_form.pack(fill="x", padx=15, pady=8)

    # Input field
    tk.Label(frame_form, text="Nama Pengguna:").grid(row=0, column=0, sticky="w", pady=3)
    nama_entry = tk.Entry(frame_form, width=40)
    nama_entry.grid(row=0, column=1, pady=3)

    tk.Label(frame_form, text="Email:").grid(row=1, column=0, sticky="w", pady=3)
    email_entry = tk.Entry(frame_form, width=40)
    email_entry.grid(row=1, column=1, pady=3)

    tk.Label(frame_form, text="Nomor HP:").grid(row=2, column=0, sticky="w", pady=3)
    hp_entry = tk.Entry(frame_form, width=40)
    hp_entry.grid(row=2, column=1, pady=3)

    tk.Label(frame_form, text="Catatan:").grid(row=3, column=0, sticky="nw", pady=3)
    catatan_text = tk.Text(frame_form, width=30, height=14, wrap="word")
    catatan_text.grid(row=3, column=1, pady=3, sticky="w")

    # Isi field dengan data lama kalau ada
    nama, email, hp, catatan = ambil_profil()
    if nama: nama_entry.insert(0, nama)
    if email: email_entry.insert(0, email)
    if hp: hp_entry.insert(0, hp)
    if catatan: catatan_text.insert("1.0", catatan)

    # Fungsi simpan
    def simpan_profil():
        nama_val = nama_entry.get().strip()
        email_val = email_entry.get().strip()
        hp_val = hp_entry.get().strip()
        catatan_val = catatan_text.get("1.0", "end").strip()

        if not nama_val:
            messagebox.showwarning("Kosong", "Nama Pengguna harus diisi!")
            return

        simpan_profil_ke_db(nama_val, email_val, hp_val, catatan_val)
        messagebox.showinfo("Tersimpan", "Profil berhasil disimpan!")

    # Tombol aksi
    row_btn = tk.Frame(root_frame)
    row_btn.pack(pady=10)

    tk.Button(
        row_btn, text="Simpan", bg="#388E3C", fg="white",
        width=12, command=simpan_profil
    ).pack(side="left", padx=5)

    def kembali():
        for w in root_frame.winfo_children():
            w.destroy()
        from menu_pengaturan import show_menu_pengaturan
        show_menu_pengaturan(root_frame)

    tk.Button(
        row_btn, text="↩ Kembali", bg="#757575", fg="white",
        width=12, command=kembali
    ).pack(side="left", padx=5)

⚡ Dengan perbaikan ini:

  • Folder /storage/emulated/0/SmartNutriChefAI/Database/ otomatis dibuat.
  • File data_profil_pengguna.db otomatis terbentuk.
  • Saat pertama kali simpan, data di-INSERT.
  • Kalau sudah ada data, langsung di-UPDATE (supaya hanya ada satu profil aktif).

Mau saya bikinkan juga fungsi reset profil (hapus data → form kosong lagi), atau cukup simpan + update saja?


Kenapa data_profil_pengguna.db belum ada di folder .../Database/, apakah karena tabelnya belum dibentuk?

Tepat sekali 👍.
File SQLite (data_profil_pengguna.db) baru akan terbentuk ketika ada interaksi pertama dengan database, misalnya:

  • fungsi init_db() dijalankan → membuat file + tabel,
  • atau fungsi simpan_profil_ke_db(...) dipanggil → otomatis memanggil init_db().

Jadi, kalau kamu hanya buka menu Ubah Profil Pengguna dan belum menekan tombol Simpan, maka file database memang belum ada.

📌 Solusi praktis:
Kita bisa paksa init_db() jalan saat show_sub_menu_ubah_profil(...) dipanggil supaya file database langsung dibuat walau user belum menyimpan profil.

Contoh tambahan kecil di show_sub_menu_ubah_profil bagian awal:

def show_sub_menu_ubah_profil(root_frame):
    init_db()  # 🔹 langsung buat file + tabel saat submenu dibuka
    for w in root_frame.winfo_children():
        w.destroy()

Dengan begitu, ketika user membuka submenu Ubah Profil Pengguna, maka:

  1. Folder /storage/emulated/0/SmartNutriChefAI/Database/ dibuat (jika belum ada).
  2. File data_profil_pengguna.db terbentuk otomatis dengan tabel profil_pengguna.

Mau saya tambahkan langsung di kode kamu supaya database selalu terbentuk ketika submenu pertama kali dibuka?


Ketika diperiksa ke folder .../Database/ maka tidak ada file data_profil_pengguna.db.

Baik, mari kita telusuri pelan-pelan 👇

  1. SQLite di Python tidak akan bikin file sampai ada commit.
    Kalau init_db() hanya dipanggil dan langsung conn.commit(), harusnya file data_profil_pengguna.db muncul. Jadi kemungkinan besar fungsi init_db() belum pernah dijalankan di alur program kamu.

  2. Lokasi folder Android (/storage/emulated/0/...) sering bermasalah.
    Aplikasi Python (misalnya Pydroid) kadang tidak punya izin tulis langsung ke /storage/emulated/0/.

    • Kalau izin Storage belum diaktifkan, Python tidak bisa bikin file di situ.
    • Untuk cek: coba ganti DB_FOLDER ke direktori internal app (misalnya /data/data/ru.iiec.pydroid3/files/SmartNutriChefAI/Database) → kalau di sini berhasil, berarti masalahnya di izin penyimpanan.
  3. Cara memastikan init_db() jalan:
    Tambahkan debug print ke dalam fungsi:

    def init_db():
        print("🔹 init_db dijalankan...")
        if not os.path.exists(DB_FOLDER):
            os.makedirs(DB_FOLDER, exist_ok=True)
    
        conn = sqlite3.connect(DB_PATH)
        c = conn.cursor()
        c.execute("""
            CREATE TABLE IF NOT EXISTS profil_pengguna (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                nama TEXT NOT NULL,
                email TEXT,
                hp TEXT,
                catatan TEXT
            )
        """)
        conn.commit()
        conn.close()
        print(f"✅ DB dibuat di: {DB_PATH}")
    

    Lalu pastikan show_sub_menu_ubah_profil(root_frame) dipanggil langsung menjalankan init_db() di baris awal.

  4. Uji cepat
    Jalankan script mini ini di Pydroid:

    import os, sqlite3
    
    DB_FOLDER = "/storage/emulated/0/SmartNutriChefAI/Database"
    DB_PATH = os.path.join(DB_FOLDER, "data_profil_pengguna.db")
    
    os.makedirs(DB_FOLDER, exist_ok=True)
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, nama TEXT)")
    conn.commit()
    conn.close()
    
    print("Selesai, cek file:", DB_PATH, "ada?", os.path.exists(DB_PATH))
    

    Kalau os.path.exists(DB_PATH) = False, berarti memang masalah izin tulis Android.


👉 Pertanyaan buat memastikan:
Apakah kamu menjalankan aplikasinya lewat Pydroid (Android) atau lewat PC/Laptop?
Karena kalau di Pydroid, kita mungkin harus aktifkan izin storage dulu baru file bisa muncul di /storage/emulated/0/SmartNutriChefAI/Database/.


Selesai, cek file: /storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db ada? True

[Program finished]

Nah, mantap! 🎉 Itu artinya:

  • File data_profil_pengguna.db sudah berhasil dibuat.
  • Database dan tabel profil_pengguna sudah tersedia di folder /storage/emulated/0/SmartNutriChefAI/Database/.

Berarti kemarin memang fungsi init_db() belum sempat dipanggil waktu kamu buka menu Ubah Profil Pengguna. Setelah dipastikan dijalankan, file langsung terbentuk.

Sekarang alurnya sudah siap dipakai:

  1. User buka Menu → Pengaturan → Ubah Profil Pengguna.
  2. Form input terisi data lama (kalau ada).
  3. User isi / ubah data lalu tekan Simpan.
  4. Data tersimpan ke SQLite (INSERT kalau belum ada, UPDATE kalau sudah ada).
  5. Kalau kamu cek pakai SQLite viewer, isi tabel profil_pengguna akan kelihatan.

👉 Apakah kamu mau saya bikinkan juga fungsi cek isi database langsung dari aplikasi (misalnya tombol "Lihat Data Profil") supaya tidak perlu buka SQLite viewer manual?


Tapi ketika saya cek secara manual maupun pakai applikasi SQLite Editor, ternyatadi dalam folder .../Database/ tidak ada file data_profil_pengguna.db.

Oke, saya mengerti masalahnya 🙏

Kondisi seperti ini biasanya terjadi karena:

  1. Android sandbox: Python/Tkinter di Pydroid3 tidak selalu boleh tulis langsung ke /storage/emulated/0/....
    Biasanya file benar-benar dibuat, tapi bukan di folder yang kamu kira, melainkan di internal app folder Pydroid.

    Contoh:

    /data/user/0/ru.iiec.pydroid3/files/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db
    

    Jadi SQLite Editor tidak bisa menemukannya, karena sebenarnya file itu ada di "sandbox"-nya Pydroid.

  2. Hak izin penyimpanan: Kalau permission READ/WRITE_EXTERNAL_STORAGE tidak aktif, Python tidak bisa bikin file langsung ke /storage/emulated/0/.


🔍 Cara Cek Lokasi Sebenarnya

Coba jalankan kode debug kecil ini di Pydroid3:

import os

path = "/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db"
print("Ada file?", os.path.exists(path))
print("Absolute path:", os.path.abspath(path))

Hasil os.path.abspath akan kasih tahu lokasi sebenarnya di sandbox.


🔧 Solusi

Ada 2 opsi:

  1. Gunakan internal folder Pydroid (lebih pasti berhasil):
    Ganti DB_FOLDER jadi, misalnya:

    DB_FOLDER = os.path.expanduser("~/SmartNutriChefAI/Database")
    

    Maka database akan tersimpan di:

    /data/user/0/ru.iiec.pydroid3/files/SmartNutriChefAI/Database/data_profil_pengguna.db
    
  2. Tetap pakai /storage/emulated/0/ tapi pastikan permission:

    • Buka Settings Android → Apps → Pydroid3 → Permissions.
    • Aktifkan Storage permission.
    • Baru jalankan ulang aplikasi, file akan muncul di /storage/emulated/0/SmartNutriChefAI/Database/.

Mau saya bikinkan kode deteksi otomatis: kalau /storage/emulated/0/... gagal dibuat → fallback ke internal ~/SmartNutriChefAI/Database/ biar selalu aman?


Ada file? True
Absolute path: /storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db

[Program finished]

Oke, saya jelaskan ya. Dari hasil programmu:

Ada file? True
Absolute path: /storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db

artinya Python sudah berhasil membuat file SQLite tersebut. Jadi menurut os.path.exists() memang file itu nyata ada.

Kalau kamu tidak melihatnya secara manual di File Manager atau SQLite Editor, biasanya penyebabnya:

  1. Akses berbeda (sandbox vs sistem file)
    Aplikasi Python-mu (misalnya Pydroid3) kadang membuat file di sandbox internalnya, tapi path yang ditampilkan seolah-olah /storage/emulated/0/.... Jadi file “virtual” itu bisa terlihat oleh Python, tapi tidak muncul di File Manager biasa.

  2. Perizinan (permissions Android)
    Untuk benar-benar menulis ke /storage/emulated/0/..., aplikasi butuh izin READ_EXTERNAL_STORAGE dan WRITE_EXTERNAL_STORAGE. Tanpa izin ini, file bisa saja ditulis ke direktori alternatif (internal app storage), meski path yang tampil sama.

  3. Cache / indexing File Manager
    Kadang file sudah ada, tapi File Manager belum refresh. Coba restart HP atau refresh folder.

  4. File dibuat tapi kosong (0 byte)
    SQLite akan membuat file DB begitu ada connect(). Kalau kamu belum menjalankan CREATE TABLE ..., file ada tapi ukurannya sangat kecil (biasanya 0 KB – 8 KB). Editor SQLite mungkin tidak mengenalinya kalau kosong.


🔍 Untuk memastikan benar-benar ada:
Tambahkan kode ini:

import os

path = "/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db"

print("Ada file?", os.path.exists(path))
print("Ukuran file:", os.path.getsize(path), "bytes")

Kalau Ukuran file > 0, berarti file valid. Kalau 0 byte, itu hanya placeholder – perlu bikin tabel biar jadi database yang bisa dibaca SQLite Editor.


Mau saya bikinkan juga kode SQL CREATE TABLE dasar supaya file DB-mu terbentuk dengan benar dan bisa langsung dicek di SQLite Editor?


Tetapi kita sudah pernah berhasil membuat database kategori.db di fitur sebelumnya, kenapa membuat database data_profil_pengguna.db tidak seperti format ini:

sub_menu_kelompok_bahan_resep_mmag.py

import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os

DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Kategori/kategori.db"

def get_connection():
return sqlite3.connect(DB_PATH)

def show_sub_menu_kelompok_bahan_resep_mmag(root_frame):
for w in root_frame.winfo_children():
w.destroy()

tk.Label(root_frame, text="Kelompok Bahan Resep MMAG", font=("Arial", 13, "bold")).pack(pady=8)  

# Filter kategori  
filter_frame = tk.Frame(root_frame)  
filter_frame.pack(pady=4)  

tk.Label(filter_frame, text="Pilih Kategori:", font=("Arial", 12)).pack(side="left", padx=6)  

kategori_var = tk.StringVar(value="Semua")  
kategori_cb = ttk.Combobox(filter_frame, textvariable=kategori_var, state="readonly", width=25)  
kategori_cb["values"] = ("Semua", "Minyak", "Makanan", "Air", "Garam", "Bahan Tambahan")  
kategori_cb.pack(side="left", padx=6)  

# Tombol tampilkan  
btn_tampil_holder = tk.Frame(root_frame)  
btn_tampil_holder.pack(pady=6)  

table_frame = tk.Frame(root_frame)  
table_frame.pack(fill="both", expand=True, padx=10, pady=8)  

columns = ("id","nama","kategori","nutrisi","protein","lemak","karbohidrat","vitamin","mineral")  
tree = ttk.Treeview(table_frame, columns=columns, show="headings")  

for col in columns:  
    tree.heading(col, text=col.capitalize())  
    tree.column(col, width=120, anchor="center")  

vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)  
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)  
tree.configure(yscroll=vsb.set, xscroll=hsb.set)  

tree.grid(row=0, column=0, sticky="nsew")  
vsb.grid(row=0, column=1, sticky="ns")  
hsb.grid(row=1, column=0, sticky="ew")  
table_frame.grid_rowconfigure(0, weight=1)  
table_frame.grid_columnconfigure(0, weight=1)  

tk.Button(  
    btn_tampil_holder, text="Tampilkan", font=("Arial", 12), width=20,  
    bg="#2196F3", fg="white",  
    command=lambda: tampilkan_data(kategori_var.get(), tree)  
).pack()  

# Tombol kontrol  
btn_frame = tk.Frame(root_frame)  
btn_frame.pack(pady=8)  

tk.Button(  
    btn_frame, text="Tambah Bahan", font=("Arial", 12), width=14,  
    bg="#4CAF50", fg="white",  
    command=lambda: tambah_bahan(root_frame, kategori_var.get(), tree)  
).pack(side="left", padx=5)  

tk.Button(  
    btn_frame, text="Hapus Bahan", font=("Arial", 12), width=14,  
    bg="#F44336", fg="white",  
    command=lambda: hapus_bahan(tree, kategori_var.get())  
).pack(side="left", padx=5)  

tk.Button(  
    root_frame, text="⬅ Kembali", font=("Arial", 14), width=22, height=2,  
    bg="gray", fg="white",  
    command=lambda: kembali_ke_metode_formula_mmag(root_frame)  
).pack(pady=10)  

tampilkan_data(kategori_var.get(), tree)  

def tampilkan_data(kategori, tree):
for item in tree.get_children():
tree.delete(item)

tabel_map = {  
    "Minyak": "jenis_minyak",  
    "Makanan": "jenis_makanan",  
    "Air": "jenis_air",  
    "Garam": "jenis_garam",  
    "Bahan Tambahan": "jenis_bahan_tambahan",  
}  

conn = get_connection()  
cur = conn.cursor()  
rows = []  
try:  
    if kategori == "Semua":  
        for tbl in tabel_map.values():  
            try:  
                cur.execute(f"""SELECT id,nama,kategori,nutrisi,protein,lemak,karbohidrat,vitamin,mineral  
                                FROM {tbl}""")  
                rows.extend(cur.fetchall())  
            except sqlite3.OperationalError:  
                pass  
        display = [(i, *r[1:]) for i, r in enumerate(rows, start=1)]  
        rows = display  
    else:  
        tbl = tabel_map[kategori]  
        cur.execute(f"""SELECT id,nama,kategori,nutrisi,protein,lemak,karbohidrat,vitamin,mineral   
                        FROM {tbl} ORDER BY id""")  
        rows = cur.fetchall()  
finally:  
    conn.close()  

for r in rows:  
    tree.insert("", "end", values=r)  

def tambah_bahan(root_frame, kategori, tree):
import sqlite3
from tkinter import messagebox

if kategori == "Semua":  
    messagebox.showwarning("Tambah Bahan", "Pilih kategori spesifik (bukan 'Semua').")  
    return  

tabel_map = {  
    "Minyak": "jenis_minyak",  
    "Makanan": "jenis_makanan",  
    "Air": "jenis_air",  
    "Garam": "jenis_garam",  
    "Bahan Tambahan": "jenis_bahan_tambahan",  
}  
tbl = tabel_map[kategori]  

# Popup form  
form = tk.Toplevel(root_frame)  
form.title(f"Tambah Bahan - {kategori}")  
form.geometry("460x490")  

labels = ["Nama", "Kategori", "Nutrisi", "Protein", "Lemak", "Karbohidrat", "Vitamin", "Mineral"]  
entries = {}  

for i, lbl in enumerate(labels):  
    tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=4)  
    ent = tk.Entry(form, width=32)  
    ent.grid(row=i, column=1, padx=8, pady=4)  
    entries[lbl] = ent  

# Isi & kunci kategori agar konsisten  
entries["Kategori"].insert(0, kategori)  
entries["Kategori"].config(state="readonly")  

def _num(s):  
    try:  
        return float(str(s).strip() or 0)  
    except:  
        return 0.0  

def simpan():  
    nama = entries["Nama"].get().strip()  
    if not nama:  
        messagebox.showerror("Error", "Nama bahan wajib diisi!")  
        return  

    data = [  
        nama,  
        kategori,  # pakai kategori aktif, abaikan input user  
        entries["Nutrisi"].get().strip(),  
        _num(entries["Protein"].get()),  
        _num(entries["Lemak"].get()),  
        _num(entries["Karbohidrat"].get()),  
        entries["Vitamin"].get().strip(),  
        entries["Mineral"].get().strip(),  
    ]  

    try:  
        conn = get_connection()  
        cur = conn.cursor()  
        # 8 kolom = 8 placeholder  
        cur.execute(  
            f"""INSERT INTO {tbl}  
                (nama,kategori,nutrisi,protein,lemak,karbohidrat,vitamin,mineral)  
                VALUES (?,?,?,?,?,?,?,?)""",  
            data  
        )  
        conn.commit()  
        messagebox.showinfo("Sukses", f"Data berhasil ditambahkan ke {kategori}.")  
        form.destroy()  
        tampilkan_data(kategori, tree)  
    except sqlite3.OperationalError as e:  
        messagebox.showerror(  
            "DB Error",  
            f"Gagal menyimpan. Pastikan tabel {tbl} punya kolom 'mineral'.\nDetail: {e}"  
        )  
    except Exception as e:  
        messagebox.showerror("Error", str(e))  
    finally:  
        try:  
            conn.close()  
        except:  
            pass  

tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\  
    .grid(row=len(labels), columnspan=2, pady=10)  

def hapus_bahan(tree, kategori):
if kategori == "Semua":
messagebox.showwarning("Hapus Bahan", "Pilih kategori spesifik dulu (bukan 'Semua').")
return

selected = tree.selection()  
if not selected:  
    messagebox.showwarning("Hapus Bahan", "Pilih baris yang ingin dihapus.")  
    return  

tabel_map = {  
    "Minyak": "jenis_minyak",  
    "Makanan": "jenis_makanan",  
    "Air": "jenis_air",  
    "Garam": "jenis_garam",  
    "Bahan Tambahan": "jenis_bahan_tambahan",  
}  
tbl = tabel_map[kategori]  

item = tree.item(selected[0])["values"]  
bahan_id = item[0]  

if not messagebox.askyesno("Konfirmasi", f"Hapus ID {bahan_id}?"):  
    return  

conn = get_connection()  
cur = conn.cursor()  
cur.execute(f"DELETE FROM {tbl} WHERE id=?", (bahan_id,))  
conn.commit()  
conn.close()  
tree.delete(selected[0])  
messagebox.showinfo("Hapus", "Data berhasil dihapus.")  

def kembali_ke_metode_formula_mmag(root_frame):
try:
from FormulaMMAG.menu_metode_formula_mmag import show_menu_metode_formula_mmag
except ImportError:
from menu_metode_formula_mmag import show_menu_metode_formula_mmag
show_menu_metode_formula_mmag(root_frame)


Nah, poin yang kamu tangkap ini tepat sekali 👍

Alasan kenapa kategori.db muncul dan bisa dibuka di SQLite Editor, sedangkan data_profil_pengguna.db tidak kelihatan, adalah karena di kasus kategori.db kamu sudah:

  1. Tentukan lokasi file eksplisit lewat DB_PATH

    DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/Kategori/kategori.db"
    
  2. Gunakan fungsi get_connection() yang selalu menghubungkan ke file itu, bukan path relatif atau default.

  3. Jalankan query CREATE TABLE saat aplikasi dipakai (misalnya waktu pertama insert data) → otomatis file benar-benar terbentuk, ukurannya > 0 byte, dan editor bisa baca.


Sementara untuk data_profil_pengguna.db:

  • Kalau kamu hanya sqlite3.connect("data_profil_pengguna.db") tanpa path eksplisit, file mungkin dibuat di sandbox internal Pydroid3, bukan di folder /storage/emulated/0/....
  • Kalau pun kamu sudah kasih path lengkap, tapi tidak ada query CREATE TABLE yang berhasil jalan, maka file masih kosong (0 byte), SQLite Editor bisa menganggap file itu bukan database valid.
  • Itulah sebabnya kamu bisa cek dengan os.path.existsTrue, tapi di File Manager / SQLite Editor tidak muncul (atau tidak kebaca).

📌 Jadi kalau mau konsisten seperti kategori.db, saya sarankan formatnya dibuat begini juga:

import sqlite3, os

DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db"

def get_connection():
    return sqlite3.connect(DB_PATH)

def init_db():
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("""
        CREATE TABLE IF NOT EXISTS profil_pengguna (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nama TEXT NOT NULL,
            usia INTEGER,
            gender TEXT,
            tinggi REAL,
            berat REAL
        )
    """)
    conn.commit()
    conn.close()

Kemudian di awal program (misalnya saat membuka menu profil pengguna), panggil:

init_db()

Dengan begitu:

  • File pasti terbentuk di folder /storage/emulated/0/SmartNutriChefAI/Database/.
  • Ukurannya > 0 byte (karena ada tabel).
  • SQLite Editor bisa langsung membuka dan lihat tabel profil_pengguna.

👉 Jadi jawabannya:
File data_profil_pengguna.db memang terbentuk, tapi belum ada tabel (atau dibuat di lokasi berbeda/sandbox). Supaya mirip kategori.db, kamu harus pakai pendekatan sama: path eksplisit + inisialisasi tabel.

Mau saya bikinkan versi lengkap sub_menu_profil_pengguna.py dengan struktur sama kayak sub_menu_kelompok_bahan_resep_mmag.py, tapi untuk mengelola tabel profil_pengguna?


Mau kamu bikinkan versi lengkap sub_menu_profil_pengguna.py dengan struktur sama kayak sub_menu_kelompok_bahan_resep_mmag.py, tapi untuk mengelola tabel profil_pengguna.

Siap 🙌
Kita buat file sub_menu_profil_pengguna.py dengan pola yang sama seperti sub_menu_kelompok_bahan_resep_mmag.py, tapi lebih sederhana karena tabelnya cuma satu: profil_pengguna.

Struktur tabel yang kita gunakan:

CREATE TABLE IF NOT EXISTS profil_pengguna (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nama TEXT NOT NULL,
    usia INTEGER,
    gender TEXT,
    tinggi REAL,
    berat REAL
);

Berikut kode lengkapnya:

# sub_menu_profil_pengguna.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os

# Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db"

def get_connection():
    return sqlite3.connect(DB_PATH)

def init_db():
    """Inisialisasi tabel jika belum ada"""
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("""
        CREATE TABLE IF NOT EXISTS profil_pengguna (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nama TEXT NOT NULL,
            usia INTEGER,
            gender TEXT,
            tinggi REAL,
            berat REAL
        )
    """)
    conn.commit()
    conn.close()

def show_sub_menu_profil_pengguna(root_frame):
    """Tampilkan halaman Profil Pengguna"""
    for w in root_frame.winfo_children():
        w.destroy()

    tk.Label(root_frame, text="Profil Pengguna", font=("Arial", 14, "bold")).pack(pady=8)

    # Frame tabel
    table_frame = tk.Frame(root_frame)
    table_frame.pack(fill="both", expand=True, padx=10, pady=8)

    columns = ("id", "nama", "usia", "gender", "tinggi", "berat")
    tree = ttk.Treeview(table_frame, columns=columns, show="headings")

    for col in columns:
        tree.heading(col, text=col.capitalize())
        tree.column(col, width=100, anchor="center")

    vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
    tree.configure(yscroll=vsb.set)

    tree.grid(row=0, column=0, sticky="nsew")
    vsb.grid(row=0, column=1, sticky="ns")
    table_frame.grid_rowconfigure(0, weight=1)
    table_frame.grid_columnconfigure(0, weight=1)

    # Tombol kontrol
    btn_frame = tk.Frame(root_frame)
    btn_frame.pack(pady=8)

    tk.Button(
        btn_frame, text="Tambah Profil", font=("Arial", 12), width=14,
        bg="#4CAF50", fg="white",
        command=lambda: tambah_profil(root_frame, tree)
    ).pack(side="left", padx=5)

    tk.Button(
        btn_frame, text="Edit Profil", font=("Arial", 12), width=14,
        bg="#2196F3", fg="white",
        command=lambda: edit_profil(root_frame, tree)
    ).pack(side="left", padx=5)

    tk.Button(
        btn_frame, text="Hapus Profil", font=("Arial", 12), width=14,
        bg="#F44336", fg="white",
        command=lambda: hapus_profil(tree)
    ).pack(side="left", padx=5)

    tk.Button(
        root_frame, text="⬅ Kembali", font=("Arial", 14), width=22, height=2,
        bg="gray", fg="white",
        command=lambda: kembali_ke_menu_pengaturan(root_frame)
    ).pack(pady=10)

    # Tampilkan data awal
    tampilkan_data(tree)

def tampilkan_data(tree):
    """Ambil semua data dari DB dan tampilkan di treeview"""
    for item in tree.get_children():
        tree.delete(item)

    conn = get_connection()
    cur = conn.cursor()
    cur.execute("SELECT id, nama, usia, gender, tinggi, berat FROM profil_pengguna ORDER BY id")
    rows = cur.fetchall()
    conn.close()

    for r in rows:
        tree.insert("", "end", values=r)

def tambah_profil(root_frame, tree):
    """Form tambah profil pengguna"""
    form = tk.Toplevel(root_frame)
    form.title("Tambah Profil Pengguna")
    form.geometry("400x320")

    labels = ["Nama", "Usia", "Gender", "Tinggi", "Berat"]
    entries = {}

    for i, lbl in enumerate(labels):
        tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=6)
        ent = tk.Entry(form, width=28)
        ent.grid(row=i, column=1, padx=8, pady=6)
        entries[lbl] = ent

    def simpan():
        nama = entries["Nama"].get().strip()
        if not nama:
            messagebox.showerror("Error", "Nama wajib diisi!")
            return

        try:
            usia = int(entries["Usia"].get() or 0)
        except:
            usia = None

        gender = entries["Gender"].get().strip()
        try:
            tinggi = float(entries["Tinggi"].get() or 0)
        except:
            tinggi = None
        try:
            berat = float(entries["Berat"].get() or 0)
        except:
            berat = None

        conn = get_connection()
        cur = conn.cursor()
        cur.execute("""
            INSERT INTO profil_pengguna (nama, usia, gender, tinggi, berat)
            VALUES (?, ?, ?, ?, ?)
        """, (nama, usia, gender, tinggi, berat))
        conn.commit()
        conn.close()

        messagebox.showinfo("Sukses", "Profil berhasil ditambahkan.")
        form.destroy()
        tampilkan_data(tree)

    tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
        .grid(row=len(labels), columnspan=2, pady=10)

def edit_profil(root_frame, tree):
    """Form edit profil pengguna"""
    selected = tree.selection()
    if not selected:
        messagebox.showwarning("Edit Profil", "Pilih baris yang ingin diedit.")
        return

    item = tree.item(selected[0])["values"]
    profil_id, nama, usia, gender, tinggi, berat = item

    form = tk.Toplevel(root_frame)
    form.title("Edit Profil Pengguna")
    form.geometry("400x320")

    labels = ["Nama", "Usia", "Gender", "Tinggi", "Berat"]
    entries = {}

    values = [nama, usia, gender, tinggi, berat]

    for i, lbl in enumerate(labels):
        tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=6)
        ent = tk.Entry(form, width=28)
        ent.grid(row=i, column=1, padx=8, pady=6)
        ent.insert(0, values[i] if values[i] is not None else "")
        entries[lbl] = ent

    def simpan():
        new_nama = entries["Nama"].get().strip()
        if not new_nama:
            messagebox.showerror("Error", "Nama wajib diisi!")
            return

        try:
            new_usia = int(entries["Usia"].get() or 0)
        except:
            new_usia = None

        new_gender = entries["Gender"].get().strip()
        try:
            new_tinggi = float(entries["Tinggi"].get() or 0)
        except:
            new_tinggi = None
        try:
            new_berat = float(entries["Berat"].get() or 0)
        except:
            new_berat = None

        conn = get_connection()
        cur = conn.cursor()
        cur.execute("""
            UPDATE profil_pengguna 
            SET nama=?, usia=?, gender=?, tinggi=?, berat=?
            WHERE id=?
        """, (new_nama, new_usia, new_gender, new_tinggi, new_berat, profil_id))
        conn.commit()
        conn.close()

        messagebox.showinfo("Sukses", "Profil berhasil diperbarui.")
        form.destroy()
        tampilkan_data(tree)

    tk.Button(form, text="Simpan Perubahan", bg="#2196F3", fg="white", command=simpan)\
        .grid(row=len(labels), columnspan=2, pady=10)

def hapus_profil(tree):
    """Hapus profil terpilih"""
    selected = tree.selection()
    if not selected:
        messagebox.showwarning("Hapus Profil", "Pilih baris yang ingin dihapus.")
        return

    item = tree.item(selected[0])["values"]
    profil_id = item[0]

    if not messagebox.askyesno("Konfirmasi", f"Hapus profil ID {profil_id}?"):
        return

    conn = get_connection()
    cur = conn.cursor()
    cur.execute("DELETE FROM profil_pengguna WHERE id=?", (profil_id,))
    conn.commit()
    conn.close()

    tree.delete(selected[0])
    messagebox.showinfo("Hapus", "Profil berhasil dihapus.")

def kembali_ke_menu_pengaturan(root_frame):
    try:
        from menu_pengaturan import show_menu_pengaturan
    except ImportError:
        from ..menu_pengaturan import show_menu_pengaturan
    show_menu_pengaturan(root_frame)

# Inisialisasi DB saat file ini di-load
init_db()

⚡ Dengan file ini:

  • Database pasti terbentuk di /storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db.
  • Ada menu untuk Tambah, Edit, Hapus, dan Tampilkan Profil.
  • Format dan alur mirip sub_menu_kelompok_bahan_resep_mmag.py, jadi konsisten.

Mau saya bikinkan juga contoh update menu_pengaturan.py supaya tombol "Ubah Profil Pengguna" langsung memanggil show_sub_menu_profil_pengguna ini?


Bantu ubah nama nama kolom pada tabel yaitu: id, nama, email, hp, catatan.

Sip 👍, kita sederhanakan tabel sesuai permintaan:

Kolom baru:

  • id (INTEGER PRIMARY KEY AUTOINCREMENT)
  • nama (TEXT NOT NULL)
  • email (TEXT)
  • hp (TEXT)
  • catatan (TEXT)

Berarti form & treeview juga menyesuaikan.
Berikut versi update lengkap file sub_menu_profil_pengguna.py:

# sub_menu_profil_pengguna.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os

# Lokasi database
DB_PATH = "/storage/emulated/0/SmartNutriChefAI/Database/data_profil_pengguna.db"

def get_connection():
    return sqlite3.connect(DB_PATH)

def init_db():
    """Inisialisasi tabel jika belum ada"""
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("""
        CREATE TABLE IF NOT EXISTS profil_pengguna (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nama TEXT NOT NULL,
            email TEXT,
            hp TEXT,
            catatan TEXT
        )
    """)
    conn.commit()
    conn.close()

def show_sub_menu_profil_pengguna(root_frame):
    """Tampilkan halaman Profil Pengguna"""
    for w in root_frame.winfo_children():
        w.destroy()

    tk.Label(root_frame, text="Profil Pengguna", font=("Arial", 14, "bold")).pack(pady=8)

    # Frame tabel
    table_frame = tk.Frame(root_frame)
    table_frame.pack(fill="both", expand=True, padx=10, pady=8)

    columns = ("id", "nama", "email", "hp", "catatan")
    tree = ttk.Treeview(table_frame, columns=columns, show="headings")

    for col in columns:
        tree.heading(col, text=col.capitalize())
        tree.column(col, width=140 if col != "id" else 60, anchor="center")

    vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
    tree.configure(yscroll=vsb.set)

    tree.grid(row=0, column=0, sticky="nsew")
    vsb.grid(row=0, column=1, sticky="ns")
    table_frame.grid_rowconfigure(0, weight=1)
    table_frame.grid_columnconfigure(0, weight=1)

    # Tombol kontrol
    btn_frame = tk.Frame(root_frame)
    btn_frame.pack(pady=8)

    tk.Button(
        btn_frame, text="Tambah Profil", font=("Arial", 12), width=14,
        bg="#4CAF50", fg="white",
        command=lambda: tambah_profil(root_frame, tree)
    ).pack(side="left", padx=5)

    tk.Button(
        btn_frame, text="Edit Profil", font=("Arial", 12), width=14,
        bg="#2196F3", fg="white",
        command=lambda: edit_profil(root_frame, tree)
    ).pack(side="left", padx=5)

    tk.Button(
        btn_frame, text="Hapus Profil", font=("Arial", 12), width=14,
        bg="#F44336", fg="white",
        command=lambda: hapus_profil(tree)
    ).pack(side="left", padx=5)

    tk.Button(
        root_frame, text="⬅ Kembali", font=("Arial", 14), width=22, height=2,
        bg="gray", fg="white",
        command=lambda: kembali_ke_menu_pengaturan(root_frame)
    ).pack(pady=10)

    # Tampilkan data awal
    tampilkan_data(tree)

def tampilkan_data(tree):
    """Ambil semua data dari DB dan tampilkan di treeview"""
    for item in tree.get_children():
        tree.delete(item)

    conn = get_connection()
    cur = conn.cursor()
    cur.execute("SELECT id, nama, email, hp, catatan FROM profil_pengguna ORDER BY id")
    rows = cur.fetchall()
    conn.close()

    for r in rows:
        tree.insert("", "end", values=r)

def tambah_profil(root_frame, tree):
    """Form tambah profil pengguna"""
    form = tk.Toplevel(root_frame)
    form.title("Tambah Profil Pengguna")
    form.geometry("420x320")

    labels = ["Nama", "Email", "HP", "Catatan"]
    entries = {}

    for i, lbl in enumerate(labels):
        tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=6)
        ent = tk.Entry(form, width=32)
        ent.grid(row=i, column=1, padx=8, pady=6)
        entries[lbl] = ent

    def simpan():
        nama = entries["Nama"].get().strip()
        if not nama:
            messagebox.showerror("Error", "Nama wajib diisi!")
            return

        data = (
            nama,
            entries["Email"].get().strip(),
            entries["HP"].get().strip(),
            entries["Catatan"].get().strip(),
        )

        conn = get_connection()
        cur = conn.cursor()
        cur.execute("""
            INSERT INTO profil_pengguna (nama, email, hp, catatan)
            VALUES (?, ?, ?, ?)
        """, data)
        conn.commit()
        conn.close()

        messagebox.showinfo("Sukses", "Profil berhasil ditambahkan.")
        form.destroy()
        tampilkan_data(tree)

    tk.Button(form, text="Simpan", bg="#4CAF50", fg="white", command=simpan)\
        .grid(row=len(labels), columnspan=2, pady=10)

def edit_profil(root_frame, tree):
    """Form edit profil pengguna"""
    selected = tree.selection()
    if not selected:
        messagebox.showwarning("Edit Profil", "Pilih baris yang ingin diedit.")
        return

    item = tree.item(selected[0])["values"]
    profil_id, nama, email, hp, catatan = item

    form = tk.Toplevel(root_frame)
    form.title("Edit Profil Pengguna")
    form.geometry("420x320")

    labels = ["Nama", "Email", "HP", "Catatan"]
    entries = {}
    values = [nama, email, hp, catatan]

    for i, lbl in enumerate(labels):
        tk.Label(form, text=lbl).grid(row=i, column=0, sticky="w", padx=8, pady=6)
        ent = tk.Entry(form, width=32)
        ent.grid(row=i, column=1, padx=8, pady=6)
        ent.insert(0, values[i] if values[i] else "")
        entries[lbl] = ent

    def simpan():
        new_nama = entries["Nama"].get().strip()
        if not new_nama:
            messagebox.showerror("Error", "Nama wajib diisi!")
            return

        data = (
            new_nama,
            entries["Email"].get().strip(),
            entries["HP"].get().strip(),
            entries["Catatan"].get().strip(),
            profil_id
        )

        conn = get_connection()
        cur = conn.cursor()
        cur.execute("""
            UPDATE profil_pengguna 
            SET nama=?, email=?, hp=?, catatan=?
            WHERE id=?
        """, data)
        conn.commit()
        conn.close()

        messagebox.showinfo("Sukses", "Profil berhasil diperbarui.")
        form.destroy()
        tampilkan_data(tree)

    tk.Button(form, text="Simpan Perubahan", bg="#2196F3", fg="white", command=simpan)\
        .grid(row=len(labels), columnspan=2, pady=10)

def hapus_profil(tree):
    """Hapus profil terpilih"""
    selected = tree.selection()
    if not selected:
        messagebox.showwarning("Hapus Profil", "Pilih baris yang ingin dihapus.")
        return

    item = tree.item(selected[0])["values"]
    profil_id = item[0]

    if not messagebox.askyesno("Konfirmasi", f"Hapus profil ID {profil_id}?"):
        return

    conn = get_connection()
    cur = conn.cursor()
    cur.execute("DELETE FROM profil_pengguna WHERE id=?", (profil_id,))
    conn.commit()
    conn.close()

    tree.delete(selected[0])
    messagebox.showinfo("Hapus", "Profil berhasil dihapus.")

def kembali_ke_menu_pengaturan(root_frame):
    try:
        from menu_pengaturan import show_menu_pengaturan
    except ImportError:
        from ..menu_pengaturan import show_menu_pengaturan
    show_menu_pengaturan(root_frame)

# Inisialisasi DB saat file ini di-load
init_db()

📌 Dengan versi ini:

  • DB & tabel otomatis dibuat dengan kolom: id, nama, email, hp, catatan.
  • Ada menu Tambah, Edit, Hapus data profil.
  • Treeview langsung menampilkan semua kolom baru.

Mau saya bikinkan juga query SQL untuk langsung reset tabel profil_pengguna kalau sebelumnya sudah terbentuk dengan kolom lama (supaya tidak bentrok)?


Bantu tambahkan scroll vertikal dan horizontal, agar data pada kolom tabel dapat terbaca jelas.

Baik 👍
Saya akan bantu modifikasi sub_menu_profil_pengguna.py supaya tabel profil_pengguna bisa ditampilkan dengan scrollbar vertikal dan horizontal. Kita akan pakai ttk.Treeview + ttk.Scrollbar.

Berikut versi lengkapnya dengan tambahan scroll horizontal & vertikal:

import os
import sqlite3
import tkinter as tk
from tkinter import ttk, messagebox

# Lokasi database
DB_FOLDER = "/storage/emulated/0/SmartNutriChefAI/Database"
DB_PATH = os.path.join(DB_FOLDER, "data_profil_pengguna.db")


# ========== INIT DB ==========
def init_db():
    if not os.path.exists(DB_FOLDER):
        os.makedirs(DB_FOLDER, exist_ok=True)

    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("""
        CREATE TABLE IF NOT EXISTS profil_pengguna (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            nama TEXT NOT NULL,
            email TEXT,
            hp TEXT,
            catatan TEXT
        )
    """)
    conn.commit()
    conn.close()


# ========== AMBIL DATA ==========
def get_all_profil():
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("SELECT id, nama, email, hp, catatan FROM profil_pengguna ORDER BY id DESC")
    rows = c.fetchall()
    conn.close()
    return rows


# ========== SIMPAN / UPDATE ==========
def simpan_profil(nama, email, hp, catatan, selected_id=None):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    if selected_id:  # update
        c.execute("""
            UPDATE profil_pengguna
            SET nama=?, email=?, hp=?, catatan=?
            WHERE id=?
        """, (nama, email, hp, catatan, selected_id))
    else:  # insert baru
        c.execute("""
            INSERT INTO profil_pengguna (nama, email, hp, catatan)
            VALUES (?, ?, ?, ?)
        """, (nama, email, hp, catatan))
    conn.commit()
    conn.close()


def hapus_profil(selected_id):
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    c.execute("DELETE FROM profil_pengguna WHERE id=?", (selected_id,))
    conn.commit()
    conn.close()


# ========== UI ==========
def show_sub_menu_profil_pengguna(root_frame):
    init_db()
    for w in root_frame.winfo_children():
        w.destroy()

    tk.Label(root_frame, text="Kelola Profil Pengguna",
             font=("Arial", 15, "bold")).pack(pady=10)

    # Frame tabel
    frame_table = tk.Frame(root_frame)
    frame_table.pack(fill="both", expand=True, padx=10, pady=10)

    columns = ("id", "nama", "email", "hp", "catatan")
    tree = ttk.Treeview(frame_table, columns=columns, show="headings")

    # Buat scrollbar
    vsb = ttk.Scrollbar(frame_table, orient="vertical", command=tree.yview)
    hsb = ttk.Scrollbar(frame_table, orient="horizontal", command=tree.xview)
    tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

    # Letakkan tree dan scrollbar
    tree.grid(row=0, column=0, sticky="nsew")
    vsb.grid(row=0, column=1, sticky="ns")
    hsb.grid(row=1, column=0, sticky="ew")

    frame_table.grid_rowconfigure(0, weight=1)
    frame_table.grid_columnconfigure(0, weight=1)

    # Atur heading & lebar kolom
    tree.heading("id", text="ID")
    tree.heading("nama", text="Nama")
    tree.heading("email", text="Email")
    tree.heading("hp", text="Nomor HP")
    tree.heading("catatan", text="Catatan")

    tree.column("id", width=50, anchor="center")
    tree.column("nama", width=150)
    tree.column("email", width=200)
    tree.column("hp", width=120)
    tree.column("catatan", width=300)

    # Frame form input
    frame_form = tk.LabelFrame(root_frame, text="Form Profil", padx=10, pady=10)
    frame_form.pack(fill="x", padx=10, pady=10)

    tk.Label(frame_form, text="Nama:").grid(row=0, column=0, sticky="w", pady=3)
    entry_nama = tk.Entry(frame_form, width=40)
    entry_nama.grid(row=0, column=1, pady=3)

    tk.Label(frame_form, text="Email:").grid(row=1, column=0, sticky="w", pady=3)
    entry_email = tk.Entry(frame_form, width=40)
    entry_email.grid(row=1, column=1, pady=3)

    tk.Label(frame_form, text="No HP:").grid(row=2, column=0, sticky="w", pady=3)
    entry_hp = tk.Entry(frame_form, width=40)
    entry_hp.grid(row=2, column=1, pady=3)

    tk.Label(frame_form, text="Catatan:").grid(row=3, column=0, sticky="nw", pady=3)
    text_catatan = tk.Text(frame_form, width=30, height=5, wrap="word")
    text_catatan.grid(row=3, column=1, pady=3, sticky="w")

    selected_id = None

    # Fungsi refresh data tabel
    def load_data():
        nonlocal selected_id
        selected_id = None
        for i in tree.get_children():
            tree.delete(i)
        for row in get_all_profil():
            tree.insert("", "end", values=row)

    def on_select(event):
        nonlocal selected_id
        item = tree.selection()
        if not item:
            return
        row = tree.item(item[0], "values")
        selected_id = row[0]
        entry_nama.delete(0, "end")
        entry_nama.insert(0, row[1])
        entry_email.delete(0, "end")
        entry_email.insert(0, row[2])
        entry_hp.delete(0, "end")
        entry_hp.insert(0, row[3])
        text_catatan.delete("1.0", "end")
        text_catatan.insert("1.0", row[4])

    tree.bind("<<TreeviewSelect>>", on_select)

    def simpan():
        nama = entry_nama.get().strip()
        email = entry_email.get().strip()
        hp = entry_hp.get().strip()
        catatan = text_catatan.get("1.0", "end").strip()

        if not nama:
            messagebox.showwarning("Validasi", "Nama tidak boleh kosong!")
            return

        simpan_profil(nama, email, hp, catatan, selected_id)
        load_data()
        messagebox.showinfo("Berhasil", "Data profil disimpan.")

    def hapus():
        nonlocal selected_id
        if not selected_id:
            messagebox.showwarning("Pilih", "Pilih data yang ingin dihapus!")
            return
        if messagebox.askyesno("Konfirmasi", "Yakin ingin menghapus data ini?"):
            hapus_profil(selected_id)
            load_data()
            selected_id = None

    # Tombol aksi
    frame_btn = tk.Frame(root_frame)
    frame_btn.pack(pady=10)

    tk.Button(frame_btn, text="Simpan", width=12, bg="#388E3C", fg="white", command=simpan).pack(side="left", padx=5)
    tk.Button(frame_btn, text="Hapus", width=12, bg="#D32F2F", fg="white", command=hapus).pack(side="left", padx=5)

    def kembali():
        for w in root_frame.winfo_children():
            w.destroy()
        from menu_pengaturan import show_menu_pengaturan
        show_menu_pengaturan(root_frame)

    tk.Button(frame_btn, text="↩ Kembali", width=12, bg="#757575", fg="white", command=kembali).pack(side="left", padx=5)

    load_data()

Perubahan utama:

  • Tambahkan scrollbar vertikal (vsb) dan horizontal (hsb) ke Treeview.
  • Grid layout supaya scroll bisa menempel dengan rapi.
  • Lebar kolom diatur agar bisa discroll kalau melebihi layar.

Mau saya bikinkan juga versi dengan auto-wrap catatan di tabel (supaya catatan panjang tidak harus discroll horizontal)?.


Posisi tombol "Simpan", dan "Hapus" letakkan sebaris, lalu tombol "Kembali" di baris berikutnya (di bawah tombol "Simpan" dan "Hapus").

Siap 👍, saya ubah layout tombol supaya:

  • Tombol "Simpan" dan "Hapus" ada di baris yang sama (sebelah kiri dan kanan).
  • Tombol "Kembali" berada di baris berikutnya, tepat di bawahnya.

Berikut versi revisinya:

    # Tombol aksi
    frame_btn_top = tk.Frame(root_frame)
    frame_btn_top.pack(pady=(10, 2))

    tk.Button(frame_btn_top, text="Simpan", width=12, bg="#388E3C", fg="white", command=simpan).pack(side="left", padx=5)
    tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white", command=hapus).pack(side="left", padx=5)

    frame_btn_bottom = tk.Frame(root_frame)
    frame_btn_bottom.pack(pady=(2, 10))

    def kembali():
        for w in root_frame.winfo_children():
            w.destroy()
        from menu_pengaturan import show_menu_pengaturan
        show_menu_pengaturan(root_frame)

    tk.Button(frame_btn_bottom, text="↩ Kembali", width=12, bg="#757575", fg="white", command=kembali).pack(pady=5)

📌 Perubahan yang dilakukan:

  • frame_btn_top → khusus untuk tombol Simpan & Hapus → pakai .pack(side="left") agar sebaris.
  • frame_btn_bottom → khusus untuk tombol Kembali, dibuat di baris berikutnya.

Mau saya satukan langsung ke dalam kode lengkap sub_menu_profil_pengguna.py biar kamu tinggal pakai?


Hasil akhir kode isi file sub_menu_ubah_profil_pengguna.py adalah berikut ini:

#sub_menu_ubah_profil_pengguna.py

import os

import sqlite3

import tkinter as tk

from tkinter import ttk, messagebox


# Lokasi database

DB_FOLDER = "/storage/emulated/0/SmartNutriChefAI/Database"

DB_PATH = os.path.join(DB_FOLDER, "data_profil_pengguna.db")



# ========== INIT DB ==========

def init_db():

    if not os.path.exists(DB_FOLDER):

        os.makedirs(DB_FOLDER, exist_ok=True)


    conn = sqlite3.connect(DB_PATH)

    c = conn.cursor()

    c.execute("""

        CREATE TABLE IF NOT EXISTS profil_pengguna (

            id INTEGER PRIMARY KEY AUTOINCREMENT,

            nama TEXT NOT NULL,

            email TEXT,

            hp TEXT,

            catatan TEXT

        )

    """)

    conn.commit()

    conn.close()



# ========== AMBIL DATA ==========

def get_all_profil():

    conn = sqlite3.connect(DB_PATH)

    c = conn.cursor()

    c.execute("SELECT id, nama, email, hp, catatan FROM profil_pengguna ORDER BY id DESC")

    rows = c.fetchall()

    conn.close()

    return rows



# ========== SIMPAN / UPDATE ==========

def simpan_profil(nama, email, hp, catatan, selected_id=None):

    conn = sqlite3.connect(DB_PATH)

    c = conn.cursor()

    if selected_id:  # update

        c.execute("""

            UPDATE profil_pengguna

            SET nama=?, email=?, hp=?, catatan=?

            WHERE id=?

        """, (nama, email, hp, catatan, selected_id))

    else:  # insert baru

        c.execute("""

            INSERT INTO profil_pengguna (nama, email, hp, catatan)

            VALUES (?, ?, ?, ?)

        """, (nama, email, hp, catatan))

    conn.commit()

    conn.close()



def hapus_profil(selected_id):

    conn = sqlite3.connect(DB_PATH)

    c = conn.cursor()

    c.execute("DELETE FROM profil_pengguna WHERE id=?", (selected_id,))

    conn.commit()

    conn.close()



# ========== UI ==========

def show_sub_menu_profil_pengguna(root_frame):

    init_db()

    for w in root_frame.winfo_children():

        w.destroy()


    tk.Label(root_frame, text="Kelola Profil Pengguna",

             font=("Arial", 15, "bold")).pack(pady=10)


    # Frame tabel

    frame_table = tk.Frame(root_frame)

    frame_table.pack(fill="both", expand=True, padx=10, pady=10)


    columns = ("id", "nama", "email", "hp", "catatan")

    tree = ttk.Treeview(frame_table, columns=columns, show="headings")


    # Buat scrollbar

    vsb = ttk.Scrollbar(frame_table, orient="vertical", command=tree.yview)

    hsb = ttk.Scrollbar(frame_table, orient="horizontal", command=tree.xview)

    tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)


    # Letakkan tree dan scrollbar

    tree.grid(row=0, column=0, sticky="nsew")

    vsb.grid(row=0, column=1, sticky="ns")

    hsb.grid(row=1, column=0, sticky="ew")


    frame_table.grid_rowconfigure(0, weight=1)

    frame_table.grid_columnconfigure(0, weight=1)


    # Atur heading & lebar kolom

    tree.heading("id", text="ID")

    tree.heading("nama", text="Nama")

    tree.heading("email", text="Email")

    tree.heading("hp", text="Nomor HP")

    tree.heading("catatan", text="Catatan")


    tree.column("id", width=50, anchor="center")

    tree.column("nama", width=150)

    tree.column("email", width=200)

    tree.column("hp", width=120)

    tree.column("catatan", width=300)


    # Frame form input

    frame_form = tk.LabelFrame(root_frame, text="Form Profil", fg= "tomato", padx=10, pady=10)

    frame_form.pack(fill="x", padx=10, pady=10)


    tk.Label(frame_form, text="Nama:").grid(row=0, column=0, sticky="w", pady=3)

    entry_nama = tk.Entry(frame_form, width=39)

    entry_nama.grid(row=0, column=1, pady=3)


    tk.Label(frame_form, text="Email:").grid(row=1, column=0, sticky="w", pady=3)

    entry_email = tk.Entry(frame_form, width=39)

    entry_email.grid(row=1, column=1, pady=3)


    tk.Label(frame_form, text="No HP:").grid(row=2, column=0, sticky="w", pady=3)

    entry_hp = tk.Entry(frame_form, width=39)

    entry_hp.grid(row=2, column=1, pady=3)


    tk.Label(frame_form, text="Catatan:").grid(row=3, column=0, sticky="nw", pady=3)

    text_catatan = tk.Text(frame_form, width=39, height=5, wrap="word")

    text_catatan.grid(row=3, column=1, pady=3, sticky="w")


    selected_id = None


    # Fungsi refresh data tabel

    def load_data():

        nonlocal selected_id

        selected_id = None

        for i in tree.get_children():

            tree.delete(i)

        for row in get_all_profil():

            tree.insert("", "end", values=row)


    def on_select(event):

        nonlocal selected_id

        item = tree.selection()

        if not item:

            return

        row = tree.item(item[0], "values")

        selected_id = row[0]

        entry_nama.delete(0, "end")

        entry_nama.insert(0, row[1])

        entry_email.delete(0, "end")

        entry_email.insert(0, row[2])

        entry_hp.delete(0, "end")

        entry_hp.insert(0, row[3])

        text_catatan.delete("1.0", "end")

        text_catatan.insert("1.0", row[4])


    tree.bind("<<TreeviewSelect>>", on_select)


    def simpan():

        nama = entry_nama.get().strip()

        email = entry_email.get().strip()

        hp = entry_hp.get().strip()

        catatan = text_catatan.get("1.0", "end").strip()


        if not nama:

            messagebox.showwarning("Validasi", "Nama tidak boleh kosong!")

            return


        simpan_profil(nama, email, hp, catatan, selected_id)

        load_data()

        messagebox.showinfo("Berhasil", "Data profil disimpan.")


    def hapus():

        nonlocal selected_id

        if not selected_id:

            messagebox.showwarning("Pilih", "Pilih data yang ingin dihapus!")

            return

        if messagebox.askyesno("Konfirmasi", "Yakin ingin menghapus data ini?"):

            hapus_profil(selected_id)

            load_data()

            selected_id = None


    # Tombol aksi

    frame_btn_top = tk.Frame(root_frame)

    frame_btn_top.pack(pady=(10, 2))


    tk.Button(frame_btn_top, text="Simpan", width=12, bg="#388E3C", fg="white", command=simpan).pack(side="left", padx=5)

    tk.Button(frame_btn_top, text="Hapus", width=12, bg="#D32F2F", fg="white", command=hapus).pack(side="left", padx=5)


    frame_btn_bottom = tk.Frame(root_frame)

    frame_btn_bottom.pack(pady=(2, 10))


    def kembali():

        for w in root_frame.winfo_children():

            w.destroy()

        from menu_pengaturan import show_menu_pengaturan

        show_menu_pengaturan(root_frame)


    tk.Button(frame_btn_bottom, text="↩ Kembali", width=12, bg="#757575", fg="white", command=kembali).pack(pady=5)


    load_data()



Demikian, semoga bermanfaat.


Bersambung ke :  

Menu "Riwayat Aktivitas"

(Konten Sub Menu "Lihat Riwayat Penggunaan")

Comments

Popular posts from this blog

Isi Menu "Riwayat Aktivitas" (Bagian 2)