Konten Sub Menu "Kondisi Khusus"

Isi Menu "Profil Kesehatan"

(Konten Sub Menu "Kondisi Khusus")


7. Sub Menu  “Kondisi Khusus”

Lanjut kita membuat konten Sub Menu "Kondisi Khusus"

Baik, kita akan buat Sub Menu "Kondisi Khusus" di Tkinter, mirip format sub menu sebelumnya, dengan fitur:

  • Form input untuk mengisi keterangan kondisi khusus (misalnya penyakit tertentu, alergi, kehamilan, dsb).
  • Data disimpan ke SQLite di folder database aplikasi.
  • Riwayat kondisi khusus tampil di layar.
  • Ada tombol kembali ke menu profil kesehatan.
  • Tampilan dibuat rapi, label rata kiri, entry di bawah label, dan bisa scroll vertikal & horizontal untuk daftar riwayat.

Berikut contoh kode sub_menu_kondisi_khusus.py:

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

DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/kondisi_khusus.db"

def buat_tabel_kondisi():
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS kondisi_khusus (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        deskripsi TEXT NOT NULL,
        tanggal TEXT NOT NULL
    )''')
    conn.commit()
    conn.close()

def simpan_kondisi(deskripsi):
    tanggal = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute("INSERT INTO kondisi_khusus (deskripsi, tanggal) VALUES (?, ?)", (deskripsi, tanggal))
    conn.commit()
    conn.close()

def ambil_kondisi():
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute("SELECT deskripsi, tanggal FROM kondisi_khusus ORDER BY id DESC")
    hasil = c.fetchall()
    conn.close()
    return hasil

def show_sub_menu_kondisi_khusus(root_frame):
    from menu_profil_kesehatan import show_menu_profil_kesehatan

    buat_tabel_kondisi()

    for widget in root_frame.winfo_children():
        widget.destroy()

    # Judul
    tk.Label(root_frame, text="Kondisi Khusus", font=("Arial", 18, "bold"),
             bg="green", fg="orange", pady=10).pack(pady=20)

    # Frame form input
    form_frame = tk.Frame(root_frame)
    form_frame.pack(pady=10, fill="x")

    tk.Label(form_frame, text="Deskripsi Kondisi Khusus:", font=("Arial", 12), anchor="w").pack(anchor="w", padx=5)
    entry_kondisi = tk.Text(form_frame, width=40, height=3)
    entry_kondisi.pack(padx=5, pady=5)

    def simpan_dan_refresh():
        deskripsi = entry_kondisi.get("1.0", tk.END).strip()
        if deskripsi:
            simpan_kondisi(deskripsi)
            entry_kondisi.delete("1.0", tk.END)
            tampilkan_kondisi()
        else:
            messagebox.showwarning("Validasi", "Deskripsi tidak boleh kosong.")

    ttk.Separator(root_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)

    # Tombol simpan
    tk.Button(root_frame, text="Simpan Kondisi", font=("Arial", 12),
              bg="#4CAF50", fg="white", command=simpan_dan_refresh).pack(pady=10)

    # Scrollable frame untuk riwayat
    container = tk.Frame(root_frame)
    container.pack(fill="both", expand=True, padx=10, pady=10)

    canvas = tk.Canvas(container)
    scrollbar_y = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
    scrollbar_x = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)
    scrollable_frame = tk.Frame(canvas)

    scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
    )

    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)

    canvas.pack(side="left", fill="both", expand=True)
    scrollbar_y.pack(side="right", fill="y")
    scrollbar_x.pack(side="bottom", fill="x")

    label_kondisi = tk.Label(scrollable_frame, text="", justify="left", font=("Arial", 11), anchor="w")
    label_kondisi.pack(anchor="w", padx=10, pady=10)

    def tampilkan_kondisi():
        kondisi_list = ambil_kondisi()
        if not kondisi_list:
            label_kondisi.config(text="Belum ada kondisi khusus yang tersimpan.")
        else:
            teks = "Daftar Kondisi Khusus:\n\n"
            for deskripsi, tanggal in kondisi_list:
                teks += f"- {deskripsi} ({tanggal})\n"
            label_kondisi.config(text=teks)

    tampilkan_kondisi()

    # Tombol kembali
    tk.Button(root_frame, text="Kembali", font=("Arial", 12),
              bg="gray", fg="white",
              command=lambda: show_menu_profil_kesehatan(root_frame)).pack(pady=20)

🔹 Fitur kode ini:

  1. Database baru kondisi_khusus.db otomatis dibuat jika belum ada.
  2. Form input: Text box tepat di bawah label, label rata kiri.
  3. Simpan data ke database dan tampilkan langsung tanpa popup.
  4. Frame daftar riwayat bisa scroll horizontal & vertikal.
  5. Tombol Kembali langsung kembali ke menu_profil_kesehatan.

Kalau mau, saya bisa langsung buatkan perubahan di menu_profil_kesehatan.py supaya tombol "Kondisi Khusus" muncul di menu.
Mau saya buatkan sekarang?


Buatkan perubahan di menu_profil_kesehatan.py supaya tombol "Kondisi Khusus" muncul di menu.

Oke, berikut adalah perubahan yang perlu kamu tambahkan pada menu_profil_kesehatan.py supaya tombol "Kondisi Khusus" muncul di menu utama profil kesehatan.

Saya buatkan bagian tombolnya menyatu dengan tombol-tombol sub menu yang sudah ada sebelumnya.

Misalnya kalau menu_profil_kesehatan.py sebelumnya seperti ini:

import tkinter as tk
from sub_menu_data_diri import show_sub_menu_data_diri
from sub_menu_berat_tinggi_badan import show_sub_menu_berat_tinggi_badan
from sub_menu_indeks_massa_tubuh import show_sub_menu_indeks_massa_tubuh
from sub_menu_riwayat_kesehatan import show_sub_menu_riwayat_kesehatan
from sub_menu_kebutuhan_kalori import show_sub_menu_kebutuhan_kalori
from sub_menu_status_gizi import show_sub_menu_status_gizi

def show_menu_profil_kesehatan(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Menu Profil Kesehatan", font=("Arial", 18, "bold"), bg="green", fg="orange", pady=10).pack(pady=20)

    # Tombol sub menu
    tk.Button(root_frame, text="Data Diri", font=("Arial", 14), width=25, command=lambda: show_sub_menu_data_diri(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Berat & Tinggi Badan", font=("Arial", 14), width=25, command=lambda: show_sub_menu_berat_tinggi_badan(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Indeks Massa Tubuh (IMT)", font=("Arial", 14), width=25, command=lambda: show_sub_menu_indeks_massa_tubuh(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Riwayat Kesehatan", font=("Arial", 14), width=25, command=lambda: show_sub_menu_riwayat_kesehatan(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Kebutuhan Kalori Harian", font=("Arial", 14), width=25, command=lambda: show_sub_menu_kebutuhan_kalori(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Status Gizi", font=("Arial", 14), width=25, command=lambda: show_sub_menu_status_gizi(root_frame)).pack(pady=5)

    # Tombol kembali
    tk.Button(root_frame, text="Kembali", font=("Arial", 14), width=25, bg="gray", fg="white", command=root_frame.quit).pack(pady=30)

Kita cukup tambahkan import dan buat tombolnya seperti ini:

from sub_menu_kondisi_khusus import show_sub_menu_kondisi_khusus

Lalu tambahkan tombolnya di antara tombol "Status Gizi" dan tombol "Kembali":

    tk.Button(root_frame, text="Kondisi Khusus", font=("Arial", 14), width=25, 
              command=lambda: show_sub_menu_kondisi_khusus(root_frame)).pack(pady=5)

Hasil akhir menu_profil_kesehatan.py (dengan tombol Kondisi Khusus):

import tkinter as tk
from sub_menu_data_diri import show_sub_menu_data_diri
from sub_menu_berat_tinggi_badan import show_sub_menu_berat_tinggi_badan
from sub_menu_indeks_massa_tubuh import show_sub_menu_indeks_massa_tubuh
from sub_menu_riwayat_kesehatan import show_sub_menu_riwayat_kesehatan
from sub_menu_kebutuhan_kalori import show_sub_menu_kebutuhan_kalori
from sub_menu_status_gizi import show_sub_menu_status_gizi
from sub_menu_kondisi_khusus import show_sub_menu_kondisi_khusus  # ← Tambahan baru

def show_menu_profil_kesehatan(root_frame):
    for widget in root_frame.winfo_children():
        widget.destroy()

    tk.Label(root_frame, text="Menu Profil Kesehatan", font=("Arial", 18, "bold"),
             bg="green", fg="orange", pady=10).pack(pady=20)

    # Tombol sub menu
    tk.Button(root_frame, text="Data Diri", font=("Arial", 14), width=25,
              command=lambda: show_sub_menu_data_diri(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Berat & Tinggi Badan", font=("Arial", 14), width=25,
              command=lambda: show_sub_menu_berat_tinggi_badan(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Indeks Massa Tubuh (IMT)", font=("Arial", 14), width=25,
              command=lambda: show_sub_menu_indeks_massa_tubuh(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Riwayat Kesehatan", font=("Arial", 14), width=25,
              command=lambda: show_sub_menu_riwayat_kesehatan(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Kebutuhan Kalori Harian", font=("Arial", 14), width=25,
              command=lambda: show_sub_menu_kebutuhan_kalori(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Status Gizi", font=("Arial", 14), width=25,
              command=lambda: show_sub_menu_status_gizi(root_frame)).pack(pady=5)
    tk.Button(root_frame, text="Kondisi Khusus", font=("Arial", 14), width=25,   # ← Tambahan tombol
              command=lambda: show_sub_menu_kondisi_khusus(root_frame)).pack(pady=5)

    # Tombol kembali
    tk.Button(root_frame, text="Kembali", font=("Arial", 14), width=25,
              bg="gray", fg="white", command=root_frame.quit).pack(pady=30)

Kalau kamu mau, saya bisa langsung bikin file sub_menu_kondisi_khusus.py ini terhubung otomatis ke database data_diri.db supaya bisa ikut memanfaatkan informasi pengguna yang sudah tersimpan.
Mau saya sambungkan sekalian?


Untuk tethubung otomatis ke database data_difi.db supaya mendapatkan informasi data pengguna yang tersimpan, kita gunakan file sub_menu_kondisi_khusus.py yang berikut ini saja. Namun kode perlu diperbaiki karena muncul error: Data pengguna tidak ditemukan.
Kode:
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
from datetime import datetime

DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/kondisi_khusus.db"

def buat_tabel_kondisi():
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS kondisi_khusus (
id INTEGER PRIMARY KEY AUTOINCREMENT,
deskripsi TEXT NOT NULL,
tanggal TEXT NOT NULL
)''')
conn.commit()
conn.close()

Ambil data pengguna dari database data_diri.db

def ambil_data_pengguna():
try:
conn = sqlite3.connect(DATA_DIRI_DB)
c = conn.cursor()
c.execute("SELECT berat, tinggi, usia, jenis_kelamin FROM data_diri ORDER BY id DESC LIMIT 1")
data = c.fetchone()
conn.close()
return data
except Exception as e:
messagebox.showerror("Error", f"Gagal mengambil data: {e}")
return None

def simpan_kondisi(deskripsi):
tanggal = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("INSERT INTO kondisi_khusus (deskripsi, tanggal) VALUES (?, ?)", (deskripsi, tanggal))
conn.commit()
conn.close()

def ambil_kondisi():
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT deskripsi, tanggal FROM kondisi_khusus ORDER BY id DESC")
hasil = c.fetchall()
conn.close()
return hasil

def show_sub_menu_kondisi_khusus(root_frame):
from menu_profil_kesehatan import show_menu_profil_kesehatan

buat_tabel_kondisi()  

for widget in root_frame.winfo_children():  
    widget.destroy()  

# Judul  
tk.Label(root_frame, text="Kondisi Khusus", font=("Arial", 18, "bold"),  
         bg="green", fg="orange", pady=10).pack(pady=20)  
           
# Tampilkan data pengguna  
data_pengguna = ambil_data_pengguna()  
if data_pengguna:  
    berat, tinggi, usia, jk = data_pengguna  
    info = f"Berat: {berat} kg | Tinggi: {tinggi} cm | Usia: {usia} th | Gender: {jk}"  
else:  
    info = "Data pengguna tidak ditemukan."  
tk.Label(root_frame, text=info, font=("Arial", 12), fg="blue").pack(pady=5)        

# Frame form input  
form_frame = tk.Frame(root_frame)  
form_frame.pack(pady=10, fill="x")  

tk.Label(form_frame, text="Deskripsi Kondisi Khusus:", font=("Arial", 12), anchor="w").pack(anchor="w", padx=5)  
entry_kondisi = tk.Text(form_frame, width=40, height=3)  
entry_kondisi.pack(padx=5, pady=5)  

def simpan_dan_refresh():  
    deskripsi = entry_kondisi.get("1.0", tk.END).strip()  
    if deskripsi:  
        simpan_kondisi(deskripsi)  
        entry_kondisi.delete("1.0", tk.END)  
        tampilkan_kondisi()  
    else:  
        messagebox.showwarning("Validasi", "Deskripsi tidak boleh kosong.")  

ttk.Separator(root_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)  

# Tombol simpan  
tk.Button(root_frame, text="Simpan Kondisi", font=("Arial", 12),  
          bg="#4CAF50", fg="white", command=simpan_dan_refresh).pack(pady=10)  

# Scrollable frame untuk riwayat  
container = tk.Frame(root_frame)  
container.pack(fill="both", expand=True, padx=10, pady=10)  

canvas = tk.Canvas(container)  
scrollbar_y = tk.Scrollbar(container, orient="vertical", command=canvas.yview)  
scrollbar_x = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)  
scrollable_frame = tk.Frame(canvas)  

scrollable_frame.bind(  
    "<Configure>",  
    lambda e: canvas.configure(scrollregion=canvas.bbox("all"))  
)  

canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")  
canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)  

canvas.pack(side="left", fill="both", expand=True)  
scrollbar_y.pack(side="right", fill="y")  
scrollbar_x.pack(side="bottom", fill="x")  

label_kondisi = tk.Label(scrollable_frame, text="", justify="left", font=("Arial", 11), anchor="w")  
label_kondisi.pack(anchor="w", padx=10, pady=10)  

def tampilkan_kondisi():  
    kondisi_list = ambil_kondisi()  
    if not kondisi_list:  
        label_kondisi.config(text="Belum ada kondisi khusus yang tersimpan.")  
    else:  
        teks = "Daftar Kondisi Khusus:\n\n"  
        for deskripsi, tanggal in kondisi_list:  
            teks += f"- {deskripsi} ({tanggal})\n"  
        label_kondisi.config(text=teks)  

tampilkan_kondisi()  

# Tombol kembali  
tk.Button(root_frame, text="Kembali", font=("Arial", 12),  
          bg="gray", fg="white",  
          command=lambda: show_menu_profil_kesehatan(root_frame)).pack(pady=20)

Saya sudah lihat masalahnya.
Error "Data pengguna tidak ditemukan" terjadi karena di kode Anda, variabel DATA_DIRI_DB belum pernah didefinisikan, tetapi dipanggil di fungsi ambil_data_pengguna().

Selain itu, jalur database data_diri.db tidak diarahkan otomatis di skrip tersebut.
Kita perlu mendefinisikan path lengkap database data_diri.db seperti yang ada di struktur folder Anda.

Berikut perbaikan kodenya supaya:

  1. Terkoneksi otomatis ke data_diri.db untuk mengambil data pengguna.
  2. Tidak error karena variabel belum didefinisikan.
  3. Struktur dan fungsi lainnya tetap sama seperti yang Anda buat.

Perbaikan sub_menu_kondisi_khusus.py

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

# Path database
DATA_DIRI_DB = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_diri.db"
DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/kondisi_khusus.db"

def buat_tabel_kondisi():
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS kondisi_khusus (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        deskripsi TEXT NOT NULL,
        tanggal TEXT NOT NULL
    )''')
    conn.commit()
    conn.close()
    
# Ambil data pengguna dari database data_diri.db
def ambil_data_pengguna():
    try:
        conn = sqlite3.connect(DATA_DIRI_DB)
        c = conn.cursor()
        c.execute("SELECT berat, tinggi, usia, jenis_kelamin FROM data_diri ORDER BY id DESC LIMIT 1")
        data = c.fetchone()
        conn.close()
        return data
    except Exception as e:
        messagebox.showerror("Error", f"Gagal mengambil data: {e}")
        return None    

def simpan_kondisi(deskripsi):
    tanggal = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute("INSERT INTO kondisi_khusus (deskripsi, tanggal) VALUES (?, ?)", (deskripsi, tanggal))
    conn.commit()
    conn.close()

def ambil_kondisi():
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute("SELECT deskripsi, tanggal FROM kondisi_khusus ORDER BY id DESC")
    hasil = c.fetchall()
    conn.close()
    return hasil

def show_sub_menu_kondisi_khusus(root_frame):
    from menu_profil_kesehatan import show_menu_profil_kesehatan

    buat_tabel_kondisi()

    for widget in root_frame.winfo_children():
        widget.destroy()

    # Judul
    tk.Label(root_frame, text="Kondisi Khusus", font=("Arial", 18, "bold"),
             bg="green", fg="orange", pady=10).pack(pady=20)
             
    # Tampilkan data pengguna
    data_pengguna = ambil_data_pengguna()
    if data_pengguna:
        berat, tinggi, usia, jk = data_pengguna
        info = f"Berat: {berat} kg | Tinggi: {tinggi} cm | Usia: {usia} th | Gender: {jk}"
    else:
        info = "Data pengguna tidak ditemukan."
    tk.Label(root_frame, text=info, font=("Arial", 12), fg="blue").pack(pady=5)      

    # Frame form input
    form_frame = tk.Frame(root_frame)
    form_frame.pack(pady=10, fill="x")

    tk.Label(form_frame, text="Deskripsi Kondisi Khusus:", font=("Arial", 12), anchor="w").pack(anchor="w", padx=5)
    entry_kondisi = tk.Text(form_frame, width=40, height=3)
    entry_kondisi.pack(padx=5, pady=5)

    def simpan_dan_refresh():
        deskripsi = entry_kondisi.get("1.0", tk.END).strip()
        if deskripsi:
            simpan_kondisi(deskripsi)
            entry_kondisi.delete("1.0", tk.END)
            tampilkan_kondisi()
        else:
            messagebox.showwarning("Validasi", "Deskripsi tidak boleh kosong.")

    ttk.Separator(root_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)

    # Tombol simpan
    tk.Button(root_frame, text="Simpan Kondisi", font=("Arial", 12),
              bg="#4CAF50", fg="white", command=simpan_dan_refresh).pack(pady=10)

    # Scrollable frame untuk riwayat
    container = tk.Frame(root_frame)
    container.pack(fill="both", expand=True, padx=10, pady=10)

    canvas = tk.Canvas(container)
    scrollbar_y = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
    scrollbar_x = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)
    scrollable_frame = tk.Frame(canvas)

    scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
    )

    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)

    canvas.pack(side="left", fill="both", expand=True)
    scrollbar_y.pack(side="right", fill="y")
    scrollbar_x.pack(side="bottom", fill="x")

    label_kondisi = tk.Label(scrollable_frame, text="", justify="left", font=("Arial", 11), anchor="w")
    label_kondisi.pack(anchor="w", padx=10, pady=10)

    def tampilkan_kondisi():
        kondisi_list = ambil_kondisi()
        if not kondisi_list:
            label_kondisi.config(text="Belum ada kondisi khusus yang tersimpan.")
        else:
            teks = "Daftar Kondisi Khusus:\n\n"
            for deskripsi, tanggal in kondisi_list:
                teks += f"- {deskripsi} ({tanggal})\n"
            label_kondisi.config(text=teks)

    tampilkan_kondisi()

    # Tombol kembali
    tk.Button(root_frame, text="Kembali", font=("Arial", 12),
              bg="gray", fg="white",
              command=lambda: show_menu_profil_kesehatan(root_frame)).pack(pady=20)

🔹 Perubahan yang saya lakukan:

  1. Menambahkan:
    DATA_DIRI_DB = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_diri.db"
    
    supaya koneksi ambil_data_pengguna() tidak error.
  2. Jalur database kondisi_khusus.db tetap seperti yang Anda buat.
  3. Struktur fungsi tetap sama, jadi integrasinya aman.

Kalau mau, saya bisa tambahkan fitur filter supaya riwayat kondisi khusus bisa ditampilkan per bulan/tahun di sini.
Itu akan memudahkan pencarian catatan lama.

Tolong tombol "kembali" dinaikkan agak ke atas, 


Tolong tombol "kembali" dinaikkan agak ke atas, buatkan tombol "Kembali" ini fixed position di bawah frame, sehingga posisinya tidak berubah walaupun isi frame di-scroll. Itu membuatnya lebih nyaman dipakai.

Oke, kalau tombol "Kembali" ingin posisinya tetap di bawah dan tidak ikut bergeser saat bagian riwayat di-scroll, caranya adalah memisahkan layout menjadi dua bagian:

  1. Bagian atas → berisi semua konten yang bisa di-scroll.
  2. Bagian bawah → berisi tombol "Kembali" yang fixed.

Berarti pada show_sub_menu_kondisi_khusus kita ganti struktur pack() menjadi model Frame atas + Frame bawah seperti ini:


Kode yang sudah diperbaiki (potongan relevan)

def show_sub_menu_kondisi_khusus(root_frame):
    from menu_profil_kesehatan import show_menu_profil_kesehatan
    buat_tabel_kondisi()

    for widget in root_frame.winfo_children():
        widget.destroy()

    # ====== Bagi frame menjadi atas dan bawah ======
    top_frame = tk.Frame(root_frame)
    top_frame.pack(fill="both", expand=True)

    bottom_frame = tk.Frame(root_frame)
    bottom_frame.pack(fill="x")

    # Judul
    tk.Label(top_frame, text="Kondisi Khusus", font=("Arial", 18, "bold"),
             bg="green", fg="orange", pady=10).pack(pady=20)

    # Data pengguna
    data_pengguna = ambil_data_pengguna()
    if data_pengguna:
        berat, tinggi, usia, jk = data_pengguna
        info = f"Berat: {berat} kg | Tinggi: {tinggi} cm | Usia: {usia} th | Gender: {jk}"
    else:
        info = "Data pengguna tidak ditemukan."
    tk.Label(top_frame, text=info, font=("Arial", 12), fg="blue").pack(pady=5)

    # Form input
    form_frame = tk.Frame(top_frame)
    form_frame.pack(pady=10, fill="x")

    tk.Label(form_frame, text="Deskripsi Kondisi Khusus:", font=("Arial", 12), anchor="w").pack(anchor="w", padx=5)
    entry_kondisi = tk.Text(form_frame, width=40, height=3)
    entry_kondisi.pack(padx=5, pady=5)

    def simpan_dan_refresh():
        deskripsi = entry_kondisi.get("1.0", tk.END).strip()
        if deskripsi:
            simpan_kondisi(deskripsi)
            entry_kondisi.delete("1.0", tk.END)
            tampilkan_kondisi()
        else:
            messagebox.showwarning("Validasi", "Deskripsi tidak boleh kosong.")

    ttk.Separator(top_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)

    # Tombol simpan
    tk.Button(top_frame, text="Simpan Kondisi", font=("Arial", 12),
              bg="#4CAF50", fg="white", command=simpan_dan_refresh).pack(pady=10)

    # Scrollable frame untuk riwayat
    container = tk.Frame(top_frame)
    container.pack(fill="both", expand=True, padx=10, pady=10)

    canvas = tk.Canvas(container)
    scrollbar_y = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
    scrollbar_x = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)
    scrollable_frame = tk.Frame(canvas)

    scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
    )

    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)

    canvas.pack(side="left", fill="both", expand=True)
    scrollbar_y.pack(side="right", fill="y")
    scrollbar_x.pack(side="bottom", fill="x")

    label_kondisi = tk.Label(scrollable_frame, text="", justify="left", font=("Arial", 11), anchor="w")
    label_kondisi.pack(anchor="w", padx=10, pady=10)

    def tampilkan_kondisi():
        kondisi_list = ambil_kondisi()
        if not kondisi_list:
            label_kondisi.config(text="Belum ada kondisi khusus yang tersimpan.")
        else:
            teks = "Daftar Kondisi Khusus:\n\n"
            for deskripsi, tanggal in kondisi_list:
                teks += f"- {deskripsi} ({tanggal})\n"
            label_kondisi.config(text=teks)

    tampilkan_kondisi()

    # ===== Tombol kembali di frame bawah (fixed) =====
    tk.Button(bottom_frame, text="Kembali", font=("Arial", 12),
              bg="gray", fg="white",
              command=lambda: show_menu_profil_kesehatan(root_frame)).pack(pady=5)

Bedanya dengan kode sebelumnya:

  • top_frame untuk semua konten yang bisa di-scroll.
  • bottom_frame hanya untuk tombol "Kembali" sehingga posisinya selalu tetap di bawah.
  • Saat scroll di area riwayat, tombol tidak ikut bergerak.

Kalau mau, saya bisa tambahkan border + warna background di bagian bawah untuk membuat tombol "Kembali" lebih menonjol seperti panel footer aplikasi.


Muncul error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/data/user/0/ru.iiec.pydroid3/files/aarch64-linux-android/lib/python3.11/tkinter/init.py", line 1948, in call
return self.func(*args)
^^^^^^^^^^^^^^^^
File "/storage/emulated/0/AppJadi/SmartNutriChefAI/menu_profil_kesehatan.py", line 52, in
command=lambda f=fungsi: f(root_frame)
^^^^^^^^^^^^^
File "/storage/emulated/0/AppJadi/SmartNutriChefAI/sub_menu_kondisi_khusus.py", line 3, in show_sub_menu_kondisi_khusus
buat_tabel_kondisi()
^^^^^^^^^^^^^^^^^^
NameError: name 'buat_tabel_kondisi' is not defined


Oke, error itu muncul karena urutan definisi fungsi di sub_menu_kondisi_khusus.py tidak tepat.

Python mengeksekusi dari atas ke bawah, dan di kode kamu, fungsi show_sub_menu_kondisi_khusus() dipanggil sebelum buat_tabel_kondisi() terdefinisi.
Akibatnya saat tombol diklik, Python belum mengenali fungsi tersebut.

Solusi: Pastikan semua fungsi (buat_tabel_kondisi, ambil_data_pengguna, simpan_kondisi, ambil_kondisi) didefinisikan di atas show_sub_menu_kondisi_khusus().

Juga, tadi di kode kamu ada bug lain — variabel DATA_DIRI_DB belum ada. Itu harus ditambahkan.


Berikut versi perbaikan full supaya:

  • Urutan fungsi benar.
  • Variabel DATA_DIRI_DB ada.
  • Tombol Kembali fixed di bawah.
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
from datetime import datetime

# Path database
DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/kondisi_khusus.db"
DATA_DIRI_DB = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_diri.db"

# ===== Fungsi Database =====
def buat_tabel_kondisi():
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS kondisi_khusus (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        deskripsi TEXT NOT NULL,
        tanggal TEXT NOT NULL
    )''')
    conn.commit()
    conn.close()

def ambil_data_pengguna():
    try:
        conn = sqlite3.connect(DATA_DIRI_DB)
        c = conn.cursor()
        c.execute("SELECT berat, tinggi, usia, jenis_kelamin FROM data_diri ORDER BY id DESC LIMIT 1")
        data = c.fetchone()
        conn.close()
        return data
    except Exception as e:
        messagebox.showerror("Error", f"Gagal mengambil data: {e}")
        return None    

def simpan_kondisi(deskripsi):
    tanggal = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute("INSERT INTO kondisi_khusus (deskripsi, tanggal) VALUES (?, ?)", (deskripsi, tanggal))
    conn.commit()
    conn.close()

def ambil_kondisi():
    conn = sqlite3.connect(DATABASE_PATH)
    c = conn.cursor()
    c.execute("SELECT deskripsi, tanggal FROM kondisi_khusus ORDER BY id DESC")
    hasil = c.fetchall()
    conn.close()
    return hasil

# ===== Fungsi Tampilan =====
def show_sub_menu_kondisi_khusus(root_frame):
    from menu_profil_kesehatan import show_menu_profil_kesehatan
    buat_tabel_kondisi()

    for widget in root_frame.winfo_children():
        widget.destroy()

    # Frame atas untuk konten scrollable
    top_frame = tk.Frame(root_frame)
    top_frame.pack(fill="both", expand=True)

    # Frame bawah untuk tombol fixed
    bottom_frame = tk.Frame(root_frame, bg="#f0f0f0")
    bottom_frame.pack(fill="x")

    # Judul
    tk.Label(top_frame, text="Kondisi Khusus", font=("Arial", 18, "bold"),
             bg="green", fg="orange", pady=10).pack(pady=20)

    # Data pengguna
    data_pengguna = ambil_data_pengguna()
    if data_pengguna:
        berat, tinggi, usia, jk = data_pengguna
        info = f"Berat: {berat} kg | Tinggi: {tinggi} cm \n| Usia: {usia} th | Gender: {jk}"
    else:
        info = "Data pengguna tidak ditemukan."
    tk.Label(top_frame, text=info, font=("Arial", 12), fg="blue").pack(pady=5)

    # Form input
    form_frame = tk.Frame(top_frame)
    form_frame.pack(pady=10, fill="x")

    tk.Label(form_frame, text="Deskripsi Kondisi Khusus:", font=("Arial", 12), anchor="w").pack(anchor="w", padx=5)
    entry_kondisi = tk.Text(form_frame, width=40, height=3)
    entry_kondisi.pack(padx=5, pady=5)

    def simpan_dan_refresh():
        deskripsi = entry_kondisi.get("1.0", tk.END).strip()
        if deskripsi:
            simpan_kondisi(deskripsi)
            entry_kondisi.delete("1.0", tk.END)
            tampilkan_kondisi()
        else:
            messagebox.showwarning("Validasi", "Deskripsi tidak boleh kosong.")

    ttk.Separator(top_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)

    # Tombol simpan
    tk.Button(top_frame, text="Simpan Kondisi", font=("Arial", 12),
              bg="#4CAF50", fg="white", command=simpan_dan_refresh).pack(pady=10)

    # Scrollable frame untuk riwayat
    container = tk.Frame(top_frame)
    container.pack(fill="both", expand=True, padx=10, pady=10)

    canvas = tk.Canvas(container)
    scrollbar_y = tk.Scrollbar(container, orient="vertical", command=canvas.yview)
    scrollbar_x = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)
    scrollable_frame = tk.Frame(canvas)

    scrollable_frame.bind(
        "<Configure>",
        lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
    )

    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
    canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)

    canvas.pack(side="left", fill="both", expand=True)
    scrollbar_y.pack(side="right", fill="y")
    scrollbar_x.pack(side="bottom", fill="x")

    label_kondisi = tk.Label(scrollable_frame, text="", justify="left", font=("Arial", 11), anchor="w")
    label_kondisi.pack(anchor="w", padx=10, pady=10)

    def tampilkan_kondisi():
        kondisi_list = ambil_kondisi()
        if not kondisi_list:
            label_kondisi.config(text="Belum ada kondisi khusus yang tersimpan.")
        else:
            teks = "Daftar Kondisi Khusus:\n\n"
            for deskripsi, tanggal in kondisi_list:
                teks += f"- {deskripsi} ({tanggal})\n"
            label_kondisi.config(text=teks)

    tampilkan_kondisi()

    # Tombol kembali fixed di bawah
    tk.Button(bottom_frame, text="Kembali", font=("Arial", 12),
              bg="gray", fg="white",
              command=lambda: show_menu_profil_kesehatan(root_frame)).pack(pady=5)

Dengan urutan seperti ini:

  • Error NameError: buat_tabel_kondisi hilang.
  • Variabel DATA_DIRI_DB sudah jelas.
  • Tombol Kembali posisinya fixed di bawah frame dan tidak ikut bergeser saat di-scroll.

Kalau mau, aku bisa tambahkan garis border atas di bottom_frame biar tombol "Kembali" kelihatan seperti panel footer.
Mau sekalian aku buatkan biar tampilannya lebih elegan?


Sampai saat ini perubahan, penyempurnaan, dan tambahan file pada applikasi SmartNutriChefAI adalah:

1. menu_profil_kesehatan.py

Kode programnya adalah berikut:


# menu_profil_kesehatan

import tkinter as tk

from tkinter import ttk

from sub_menu_data_diri import show_sub_menu_data_diri

from sub_menu_berat_tinggi_badan import show_sub_menu_berat_tinggi_badan

from sub_menu_indeks_massa_tubuh import show_sub_menu_indeks_massa_tubuh

from sub_menu_riwayat_kesehatan import show_sub_menu_riwayat_kesehatan

from sub_menu_kebutuhan_kalori import show_sub_menu_kebutuhan_kalori

from sub_menu_status_gizi import show_sub_menu_status_gizi

from sub_menu_kondisi_khusus import show_sub_menu_kondisi_khusus


def show_menu_profil_kesehatan(root_frame):

    from menu_utama import tampilkan_halaman_utama  # Hindari circular import


    for widget in root_frame.winfo_children():

        widget.destroy()


    # Judul

    tk.Label(

        root_frame,

        text="Profil Kesehatan",

        font=("Arial", 18, "bold"),

        bg="green",

        fg="orange",

        padx=10,

        pady=10

    ).pack(pady=20)


    # Daftar submenu sebagai list tuple: (teks tombol, fungsi yang dipanggil)

    submenus = [

        ("Data Diri", show_sub_menu_data_diri),

        ("Berat & Tinggi Badan", show_sub_menu_berat_tinggi_badan),

        # Tambahkan submenu lain di sini, contoh:

        # ("Kalori Harian", show_sub_menu_kalori_harian),

        # ("Riwayat BMI", show_sub_menu_riwayat_bmi),

  

        ("Indeks Massa Tubuh \n(IMT)", show_sub_menu_indeks_massa_tubuh),  # Tambahkan ini

        ("Riwayat Kesehatan", show_sub_menu_riwayat_kesehatan),

        ("Kebutuhan Kalori Harian", show_sub_menu_kebutuhan_kalori),

        ("Status Gizi", show_sub_menu_status_gizi),

        ("Kondisi Khusus", show_sub_menu_kondisi_khusus),

]


    # Buat tombol-tombol sub menu

    for teks, fungsi in submenus:

        tk.Button(

            root_frame,

            text=teks,

            font=("Arial", 12),

            bg="light blue",

            fg="black",

            command=lambda f=fungsi: f(root_frame)

        ).pack(pady=10)


    # Tambahkan separator antar tombol (kecuali terakhir)

    ttk.Separator(root_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)


    # Tombol kembali

    tk.Button(

        root_frame,

        text="Kembali ke Menu Utama",

        font=("Arial", 12),

        bg="gray",

        fg="white",

        command=lambda: tampilkan_halaman_utama(root_frame)

    ).pack(pady=30)


2. sub_menu_kondisi_khusus.py

Kode programnya adalah berikut:


# Sub Menu Kondisi Khusus

import tkinter as tk

from tkinter import ttk, messagebox

import sqlite3

from datetime import datetime


# Path database

DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/kondisi_khusus.db"

DATA_DIRI_DB = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_diri.db"


# ===== Fungsi Database =====

def buat_tabel_kondisi():

    conn = sqlite3.connect(DATABASE_PATH)

    c = conn.cursor()

    c.execute('''CREATE TABLE IF NOT EXISTS kondisi_khusus (

        id INTEGER PRIMARY KEY AUTOINCREMENT,

        deskripsi TEXT NOT NULL,

        tanggal TEXT NOT NULL

    )''')

    conn.commit()

    conn.close()


def ambil_data_pengguna():

    try:

        conn = sqlite3.connect(DATA_DIRI_DB)

        c = conn.cursor()

        c.execute("SELECT berat, tinggi, usia, jenis_kelamin FROM data_diri ORDER BY id DESC LIMIT 1")

        data = c.fetchone()

        conn.close()

        return data

    except Exception as e:

        messagebox.showerror("Error", f"Gagal mengambil data: {e}")

        return None    


def simpan_kondisi(deskripsi):

    tanggal = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    conn = sqlite3.connect(DATABASE_PATH)

    c = conn.cursor()

    c.execute("INSERT INTO kondisi_khusus (deskripsi, tanggal) VALUES (?, ?)", (deskripsi, tanggal))

    conn.commit()

    conn.close()


def ambil_kondisi():

    conn = sqlite3.connect(DATABASE_PATH)

    c = conn.cursor()

    c.execute("SELECT deskripsi, tanggal FROM kondisi_khusus ORDER BY id DESC")

    hasil = c.fetchall()

    conn.close()

    return hasil


# ===== Fungsi Tampilan =====

def show_sub_menu_kondisi_khusus(root_frame):

    from menu_profil_kesehatan import show_menu_profil_kesehatan

    buat_tabel_kondisi()


    for widget in root_frame.winfo_children():

        widget.destroy()


    # Frame atas untuk konten scrollable

    top_frame = tk.Frame(root_frame)

    top_frame.pack(fill="both", expand=True)


    # Frame bawah untuk tombol fixed

    bottom_frame = tk.Frame(root_frame, bg="#f0f0f0")

    bottom_frame.pack(fill="x")


    # Judul

    tk.Label(top_frame, text="Kondisi Khusus", font=("Arial", 18, "bold"),

             bg="green", fg="orange", pady=10).pack(pady=20)


    # Data pengguna

    data_pengguna = ambil_data_pengguna()

    if data_pengguna:

        berat, tinggi, usia, jk = data_pengguna

        info = f"Berat: {berat} kg | Tinggi: {tinggi} cm \n| Usia: {usia} th | Gender: {jk}"

    else:

        info = "Data pengguna tidak ditemukan."

    tk.Label(top_frame, text=info, font=("Arial", 12), fg="blue").pack(pady=5)


    # Form input

    form_frame = tk.Frame(top_frame)

    form_frame.pack(pady=10, fill="x")


    tk.Label(form_frame, text="Deskripsi Kondisi Khusus:", font=("Arial", 12), anchor="w").pack(anchor="w", padx=5)

    entry_kondisi = tk.Text(form_frame, width=40, height=3)

    entry_kondisi.pack(padx=5, pady=5)


    def simpan_dan_refresh():

        deskripsi = entry_kondisi.get("1.0", tk.END).strip()

        if deskripsi:

            simpan_kondisi(deskripsi)

            entry_kondisi.delete("1.0", tk.END)

            tampilkan_kondisi()

        else:

            messagebox.showwarning("Validasi", "Deskripsi tidak boleh kosong.")


    ttk.Separator(top_frame, orient='horizontal').pack(fill='x', padx=40, pady=2)


    # Tombol simpan

    tk.Button(top_frame, text="Simpan Kondisi", font=("Arial", 12),

              bg="#4CAF50", fg="white", command=simpan_dan_refresh).pack(pady=10)


    # Scrollable frame untuk riwayat

    container = tk.Frame(top_frame)

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


    canvas = tk.Canvas(container)

    scrollbar_y = tk.Scrollbar(container, orient="vertical", command=canvas.yview)

    scrollbar_x = tk.Scrollbar(container, orient="horizontal", command=canvas.xview)

    scrollable_frame = tk.Frame(canvas)


    scrollable_frame.bind(

        "<Configure>",

        lambda e: canvas.configure(scrollregion=canvas.bbox("all"))

    )


    canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")

    canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)


    canvas.pack(side="left", fill="both", expand=True)

    scrollbar_y.pack(side="right", fill="y")

    scrollbar_x.pack(side="bottom", fill="x")


    label_kondisi = tk.Label(scrollable_frame, text="", justify="left", font=("Arial", 11), anchor="w")

    label_kondisi.pack(anchor="w", padx=10, pady=10)


    def tampilkan_kondisi():

        kondisi_list = ambil_kondisi()

        if not kondisi_list:

            label_kondisi.config(text="Belum ada kondisi khusus yang tersimpan.")

        else:

            teks = "Daftar Kondisi Khusus:\n\n"

            for deskripsi, tanggal in kondisi_list:

                teks += f"- {deskripsi} ({tanggal})\n"

            label_kondisi.config(text=teks)


    tampilkan_kondisi()


    # Tombol kembali fixed di bawah

    tk.Button(bottom_frame, text="Kembali", font=("Arial", 12),

              bg="gray", fg="white",

              command=lambda: show_menu_profil_kesehatan(root_frame)).pack(pady=5)


Bersambung Ke:   Isi Menu "Data Bahan Makanan"


Comments

Popular posts from this blog

Isi Menu "Setting"

Isi Menu "Riwayat Aktivitas" (Bagian 2)