Tambahan Sub Menu Lainnya Pada "Menu Data Bahan Makanan"
TAMBAHAN SUB MENU LAINNYA PADA "MENU DATA BAHAN MAKANAN"
Kira-kira Konten apa lagi yang perlu kita tambahkan pada "Menu Data Bahan Makanan" ini sebagai tambahan sub menu di bawah menu ini.
Kalau kita mau bikin Menu Data Bahan Makanan ini jadi benar-benar kaya fitur dan praktis, saya usulkan beberapa sub menu tambahan supaya fungsinya lebih lengkap, tidak cuma lihat daftar & cari gizi saja.
Berikut idenya:
📋 Usulan Sub Menu Tambahan
-
📦 Kelompokkan Bahan Makanan
- Fungsi: Menampilkan bahan makanan berdasarkan kategori, misalnya:
- Sumber Karbohidrat
- Sumber Protein Hewani
- Sumber Protein Nabati
- Sayur-sayuran
- Buah-buahan
- Manfaat: Memudahkan pencarian bahan sesuai jenisnya.
- Fungsi: Menampilkan bahan makanan berdasarkan kategori, misalnya:
-
📊 Analisis Kombinasi Bahan
- Fungsi: Pilih beberapa bahan → aplikasi menghitung total gizi (Kalori, Protein, Lemak, Karbohidrat, Vitamin) gabungan.
- Manfaat: Berguna untuk merencanakan menu harian.
-
📈 Ranking Bahan Berdasarkan Gizi Tertentu
- Fungsi: Urutkan bahan berdasarkan kandungan tertinggi untuk gizi tertentu (misal: protein terbanyak).
- Manfaat: Membantu memilih bahan dengan kualitas gizi terbaik.
-
📝 Edit & Perbarui Data Bahan
- Fungsi: Mengubah data gizi bahan yang sudah ada (misal: memperbaiki kesalahan atau update data).
- Manfaat: Database tetap akurat dan up to date.
-
🗑 Hapus Semua Data Bahan
- Fungsi: Menghapus semua data di database sekaligus (dengan konfirmasi keamanan).
- Manfaat: Reset database untuk kebutuhan baru.
-
📤 Ekspor Data Bahan ke CSV
- Fungsi: Menyimpan semua data bahan ke file CSV agar bisa dibuka di Excel.
- Manfaat: Memudahkan backup dan berbagi data.
Kalau menurut saya, fitur #2 (Analisis Kombinasi Bahan) dan fitur #3 (Ranking Bahan) itu akan bikin aplikasi ini terasa jauh lebih pintar, karena ini sudah masuk ke level "decision support" buat pengguna.
Kalau kamu mau, saya bisa langsung susun urutan Menu Data Bahan Makanan yang sudah berisi semua sub menu ini dan sambil buatkan kerangka file-filenya.
Begitu kamu eksekusi, semua tombol akan muncul meskipun isinya nanti kita isi bertahap.
Mau saya susun strukturnya sekarang?
Isi Menu "Data Bahan Makanan"
(Konten Sub Menu "Kelompok Bahan Makanan")
3. Sub Menu “Kelompok Bahan Makanan"
Sekarang kita masuk ke Menu Data Bahan Makanan, kita lanjut membuat konten Sub Menu "Kelompok Bahan Makanan"
Bagus — kita mulai dari Sub Menu "Kelompok Bahan Makanan".
Saya buatkan file lengkap sub_menu_kelompokkan_bahan.py yang:
- Menambahkan kolom
kategoripada tabelbahan_makananbila belum ada (safeALTER TABLE). - Menampilkan daftar bahan beserta kategorinya (Treeview).
- Bisa filter berdasarkan kategori.
- Bisa assign category ke satu / banyak item sekaligus (popup pilih category atau tulis baru).
- Bisa remove category (mengosongkan kategori pada item terpilih).
- Tombol Segarkan dan Kembali.
Salin-tempel isi berikut ke file sub_menu_kelompokkan_bahan.py di folder project kamu.
# sub_menu_kelompokkan_bahan.py
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import sqlite3
import os
DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_bahan_makanan.db"
# ======== Helper DB ========
def ensure_db_and_kategori():
os.makedirs(os.path.dirname(DATABASE_PATH), exist_ok=True)
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
# buat tabel dasar bila belum ada
c.execute("""
CREATE TABLE IF NOT EXISTS bahan_makanan (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT NOT NULL,
kalori REAL,
protein REAL,
lemak REAL,
karbohidrat REAL,
vitamin TEXT
)
""")
# periksa kolom kategori, tambahkan jika belum ada
c.execute("PRAGMA table_info(bahan_makanan)")
cols = [row[1] for row in c.fetchall()] # row: (cid,name,type,...)
if "kategori" not in cols:
try:
c.execute("ALTER TABLE bahan_makanan ADD COLUMN kategori TEXT DEFAULT 'Uncategorized'")
except Exception:
# safe fallback: jika gagal, abaikan (biasanya tidak terjadi)
pass
conn.commit()
conn.close()
def get_categories():
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("SELECT DISTINCT kategori FROM bahan_makanan WHERE kategori IS NOT NULL AND kategori != '' ORDER BY kategori COLLATE NOCASE")
rows = c.fetchall()
conn.close()
cats = [r[0] for r in rows if r[0] is not None]
# letakkan opsi Semua di depan
if "Uncategorized" in cats:
# biarkan "Uncategorized" tetap, tapi taruh setelah "Semua"
cats = [c for c in cats if c != "Uncategorized"]
cats.insert(0, "Uncategorized")
cats.insert(0, "Semua")
return cats
def get_items_by_category(kategori=None):
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
if kategori and kategori != "Semua":
c.execute("""
SELECT id, nama, IFNULL(kategori, ''), kalori, protein, lemak, karbohidrat, IFNULL(vitamin,'')
FROM bahan_makanan WHERE kategori = ? ORDER BY nama COLLATE NOCASE
""", (kategori,))
else:
c.execute("""
SELECT id, nama, IFNULL(kategori, ''), kalori, protein, lemak, karbohidrat, IFNULL(vitamin,'')
FROM bahan_makanan ORDER BY nama COLLATE NOCASE
""")
rows = c.fetchall()
conn.close()
return rows
def update_category_for_ids(ids, kategori):
if not ids:
return
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
for _id in ids:
c.execute("UPDATE bahan_makanan SET kategori = ? WHERE id = ?", (kategori, _id))
conn.commit()
conn.close()
def remove_category_for_ids(ids):
if not ids:
return
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
for _id in ids:
c.execute("UPDATE bahan_makanan SET kategori = '' WHERE id = ?", (_id,))
conn.commit()
conn.close()
# ======== UI ========
def show_sub_menu_kelompokkan_bahan(root_frame):
from menu_data_bahan_makanan import show_menu_data_bahan_makanan # import lokal untuk hindari circular import
ensure_db_and_kategori()
# clear frame
for w in root_frame.winfo_children():
w.destroy()
# Judul (sub menu — gunakan background biru terang & teks hijau sesuai permintaan sebelumnya)
header = tk.Label(root_frame, text="Kelompok Bahan Makanan",
font=("Arial", 18, "bold"),
bg="light blue", fg="green", pady=8)
header.pack(fill="x")
# Filter frame (pilih kategori)
filter_frame = tk.Frame(root_frame)
filter_frame.pack(fill="x", pady=8, padx=8)
tk.Label(filter_frame, text="Filter kategori:", font=("Arial", 12)).pack(side="left", padx=(0,6))
kategori_var = tk.StringVar()
cb_kategori = ttk.Combobox(filter_frame, textvariable=kategori_var, state="readonly", width=25)
cb_kategori.pack(side="left")
def refresh_categories():
cats = get_categories()
cb_kategori['values'] = cats
if cats:
cb_kategori.set(cats[0])
refresh_categories()
def aksi_filter():
load_table(cb_kategori.get())
tk.Button(filter_frame, text="Tampilkan", bg="#4CAF50", fg="white", command=aksi_filter).pack(side="left", padx=6)
tk.Button(filter_frame, text="Segarkan Kategori", command=refresh_categories).pack(side="left", padx=6)
# Table frame with scrollbars
table_frame = tk.Frame(root_frame)
table_frame.pack(fill="both", expand=True, padx=8, pady=(0,8))
tree_cols = ("ID", "Nama", "Kategori", "Kalori\n(kkal)", "Protein\n(g)", "Lemak\n(g)", "Karbohidrat\n(g)", "Vitamin")
tree = ttk.Treeview(table_frame, columns=tree_cols, show="headings", selectmode="extended")
# headings
for col in tree_cols:
tree.heading(col, text=col)
# columns width & alignment
tree.column("ID", width=50, anchor="center")
tree.column("Nama", width=220, anchor="w")
tree.column("Kategori", width=140, anchor="center")
tree.column("Kalori\n(kkal)", width=100, anchor="center")
tree.column("Protein\n(g)", width=100, anchor="center")
tree.column("Lemak\n(g)", width=100, anchor="center")
tree.column("Karbohidrat\n(g)", width=120, anchor="center")
tree.column("Vitamin", width=140, anchor="center")
# scrollbars
vsb = ttk.Scrollbar(table_frame, orient="vertical", command=tree.yview)
hsb = ttk.Scrollbar(table_frame, orient="horizontal", command=tree.xview)
tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
vsb.pack(side="right", fill="y")
hsb.pack(side="bottom", fill="x")
tree.pack(fill="both", expand=True, side="left")
# fungsi load data ke tabel
def load_table(kategori=None):
tree.delete(*tree.get_children())
rows = get_items_by_category(kategori)
if not rows:
# tampil pesan kecil di tabel (insert dummy row) atau biarkan kosong
return
for r in rows:
# r = (id, nama, kategori, kalori, protein, lemak, karbohidrat, vitamin)
tree.insert("", "end", values=r)
load_table() # load all awal
# Buttons bawah tabel (assign / remove / refresh)
btns_frame = tk.Frame(root_frame)
btns_frame.pack(fill="x", pady=6)
def open_assign_popup():
sel = tree.selection()
if not sel:
messagebox.showwarning("Peringatan", "Pilih paling sedikit satu bahan untuk diberi kategori.")
return
# ambil daftar kategori saat ini + opsi buat baru
cats = get_categories()
# popup
popup = tk.Toplevel(root_frame)
popup.title("Assign Category")
popup.geometry("360x160")
tk.Label(popup, text="Pilih kategori (atau tulis kategori baru):").pack(pady=6)
combo_var = tk.StringVar()
combo = ttk.Combobox(popup, textvariable=combo_var, values=cats, state="normal", width=30)
combo.pack(pady=4)
combo.set(cats[0] if cats else "")
entry_new = tk.Entry(popup, width=30)
entry_new.pack(pady=4)
entry_new.insert(0, "") # kosong default
def apply_category():
chosen = entry_new.get().strip() or combo_var.get().strip()
if not chosen:
messagebox.showwarning("Peringatan", "Kategori tidak boleh kosong.")
return
ids = []
for it in sel:
vals = tree.item(it, "values")
try:
ids.append(int(vals[0]))
except Exception:
pass
if not ids:
messagebox.showerror("Error", "Gagal membaca data yang dipilih.")
popup.destroy()
return
update_category_for_ids(ids, chosen)
popup.destroy()
refresh_categories()
load_table(cb_kategori.get())
tk.Button(popup, text="Terapkan", bg="#4CAF50", fg="white", command=apply_category).pack(pady=8)
def remove_category_selected():
sel = tree.selection()
if not sel:
messagebox.showwarning("Peringatan", "Pilih paling sedikit satu bahan untuk dihapus kategorinya.")
return
if not messagebox.askyesno("Konfirmasi", "Kosongkan kategori untuk item yang terpilih?"):
return
ids = []
for it in sel:
vals = tree.item(it, "values")
try:
ids.append(int(vals[0]))
except Exception:
pass
remove_category_for_ids(ids)
refresh_categories()
load_table(cb_kategori.get())
def segarkan_semua():
refresh_categories()
load_table(cb_kategori.get())
tk.Button(btns_frame, text="Assign Category ke Terpilih", bg="#1976D2", fg="white", command=open_assign_popup).pack(side="left", padx=6)
tk.Button(btns_frame, text="Hapus Kategori dari Terpilih", bg="#D32F2F", fg="white", command=remove_category_selected).pack(side="left", padx=6)
tk.Button(btns_frame, text="Segarkan", bg="#4CAF50", fg="white", command=segarkan_semua).pack(side="left", padx=6)
# Tombol Kembali fixed di bawah
footer = tk.Frame(root_frame)
footer.pack(fill="x", side="bottom")
tk.Button(footer, text="Kembali ke Menu Data Bahan Makanan", font=("Arial", 14),
bg="gray", fg="white", width=35, height=2,
command=lambda: show_menu_data_bahan_makanan(root_frame)).pack(pady=6)
# Binding double-click: edit category cepat
def on_double_click(event):
item = tree.identify_row(event.y)
if not item:
return
vals = tree.item(item, "values")
try:
item_id = int(vals[0])
except Exception:
return
# minta kategori baru
new_cat = simpledialog.askstring("Edit Kategori", f"Masukkan kategori untuk '{vals[1]}' (kosong = hapus):")
if new_cat is None:
return
if new_cat.strip() == "":
remove_category_for_ids([item_id])
else:
update_category_for_ids([item_id], new_cat.strip())
segarkan_semua()
tree.bind("<Double-1>", on_double_click)
Kalau kamu mau, langkah selanjutnya saya bisa:
- Tambahkan validasi & hint (mis. warna baris bila
kategori==''→ tampilkanUncategorizeddengan warna khusus). - Buat export kategori ke CSV.
- Buat tampilan pra-definisi kategori (mis. kelompok: Karbohidrat / Protein Hewani / Sayuran / Buah / Lemak) agar pengguna bisa memilih dari daftar kategori standar.
Mau lanjut pakai salah satu tambahan itu sekarang?
Bagaimana dengan perubahan kode pada isi file "menu_data_bahan_makanan.py"
Untuk menambahkan Sub Menu "Kelompok Bahan Makanan" ke dalam menu_data_bahan_makanan.py, kamu hanya perlu:
- Menambahkan tombol atau item menu yang memanggil fungsi
show_sub_menu_kelompokkan_bahan()dari filesub_menu_kelompokkan_bahan.py. - Pastikan import dilakukan secara lokal (di dalam fungsi) supaya tidak terjadi circular import.
Contoh update menu_data_bahan_makanan.py:
# menu_data_bahan_makanan.py
import tkinter as tk
def show_menu_data_bahan_makanan(root_frame):
# Bersihkan frame
for widget in root_frame.winfo_children():
widget.destroy()
header = tk.Label(root_frame, text="Menu Data Bahan Makanan",
font=("Arial", 18, "bold"),
bg="light blue", fg="green", pady=8)
header.pack(fill="x")
menu_frame = tk.Frame(root_frame)
menu_frame.pack(fill="both", expand=True, pady=20)
# Tombol: Lihat Daftar Bahan
tk.Button(menu_frame, text="Lihat Daftar Bahan", font=("Arial", 14),
width=35, height=2,
command=lambda: __import__('sub_menu_lihat_daftar_bahan').show_sub_menu_lihat_daftar_bahan(root_frame)).pack(pady=6)
# Tombol: Cari Bahan Berdasarkan Gizi
tk.Button(menu_frame, text="Cari Bahan Berdasarkan Gizi", font=("Arial", 14),
width=35, height=2,
command=lambda: __import__('sub_menu_cari_bahan_gizi').show_sub_menu_cari_bahan_gizi(root_frame)).pack(pady=6)
# Tombol: Kelompok Bahan Makanan (baru ditambahkan)
tk.Button(menu_frame, text="Kelompok Bahan Makanan", font=("Arial", 14),
width=35, height=2,
command=lambda: __import__('sub_menu_kelompokkan_bahan').show_sub_menu_kelompokkan_bahan(root_frame)).pack(pady=6)
# Tombol kembali ke menu utama
tk.Button(menu_frame, text="Kembali ke Menu Utama", font=("Arial", 14),
width=35, height=2,
command=lambda: __import__('menu_utama').show_menu_utama(root_frame)).pack(pady=6)
Perubahan penting:
- Saya pakai
__import__('nama_file')untuk menghindari import di awal file, sehingga modul hanya di-load ketika dibutuhkan → ini mencegah masalah circular import. - Tambahan tombol Kelompok Bahan Makanan memanggil
show_sub_menu_kelompokkan_bahan()dari file yang tadi kita buat.
Kalau mau, saya bisa langsung buatkan versi lengkap menu_data_bahan_makanan.py yang sudah sinkron dengan semua sub menu yang kita rancang, termasuk Cari Bahan Berdasarkan Gizi dan sub menu lainnya.
Mau saya buatkan yang versi lengkap itu?
Tolong kamu rapikan dan persingkat kode isi file "menu_data_bahan_makanan.py" berikut ini:
# Menu Data Bahan Makanan
import tkinter as tk
from DataBahanMakanan.sub_menu_lihat_daftar_bahan import show_sub_menu_lihat_daftar_bahan
from DataBahanMakanan.sub_menu_cari_data_gizi import show_sub_menu_cari_data_gizi
from DataBahanMakanan.sub_menu_kelompokkan_bahan import show_sub_menu_kelompokkan_bahan
def show_menu_data_bahan_makanan(root_frame):
# Hapus semua widget yang ada
for widget in root_frame.winfo_children():
widget.destroy()
# Judul menu
tk.Label(root_frame, text="Data Bahan Makanan", font=("Arial", 18, "bold"),
bg="green", fg="white", pady=10).pack(pady=20)
# Tombol ke sub menu "Lihat Daftar Bahan"
tk.Button(root_frame, text="Lihat Daftar Bahan", font=("Arial", 14),
bg="#4CAF50", fg="white", width=25, height=2,
command=lambda: show_sub_menu_lihat_daftar_bahan(root_frame)).pack(pady=10)
tk.Button(root_frame, text="Cari Data Gizi", font=("Arial", 14),
bg="#4CAF50", fg="white", width=25, height=2,
command=lambda: show_sub_menu_cari_data_gizi(root_frame)).pack(pady=10)
tk.Button(root_frame, text="Kelompokkan Bahan", font=("Arial", 14),
bg="#4CAF50", fg="white", width=25, height=2,
command=lambda: show_sub_menu_kelompokkan_bahan(root_frame)).pack(pady=10)
# Import dari menu_utama.py
from menu_utama import tampilkan_halaman_utama
# Tombol kembali ke menu utama
tk.Button(root_frame, text="Kembali Ke Menu Utama", font=("Arial", 14),
bg="gray", fg="white", width=25, height=2,
command=lambda: tampilkan_halaman_utama(root_frame)).pack(pady=20)
Kalau mau dirapikan dan dipersingkat, kode menu_data_bahan_makanan.py bisa dibuat seperti ini:
# Menu Data Bahan Makanan
import tkinter as tk
from DataBahanMakanan import (
sub_menu_lihat_daftar_bahan as lihat,
sub_menu_cari_data_gizi as cari,
sub_menu_kelompokkan_bahan as kelompok
)
def show_menu_data_bahan_makanan(root_frame):
# Hapus widget lama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
tk.Label(root_frame, text="Data Bahan Makanan",
font=("Arial", 18, "bold"),
bg="green", fg="white", pady=10).pack(pady=20)
# Daftar tombol sub menu
menu_items = [
("Lihat Daftar Bahan", lihat.show_sub_menu_lihat_daftar_bahan),
("Cari Data Gizi", cari.show_sub_menu_cari_data_gizi),
("Kelompokkan Bahan", kelompok.show_sub_menu_kelompokkan_bahan),
]
for text, cmd in menu_items:
tk.Button(root_frame, text=text, font=("Arial", 14),
bg="#4CAF50", fg="white", width=25, height=2,
command=lambda c=cmd: c(root_frame)).pack(pady=10)
# Tombol kembali
from menu_utama import tampilkan_halaman_utama
tk.Button(root_frame, text="Kembali Ke Menu Utama",
font=("Arial", 14),
bg="gray", fg="white", width=25, height=2,
command=lambda: tampilkan_halaman_utama(root_frame)).pack(pady=20)
Perbedaan dan keunggulan versi ini:
- Lebih ringkas karena daftar tombol sub menu disusun dalam list
menu_items. - Menghindari pengulangan kode — tidak perlu tulis
tk.Button(...)berulang kali untuk setiap menu. - Tetap mempertahankan tampilan dan fungsi yang sama seperti versi awal.
Kalau kamu mau, setelah ini saya bisa bikin semua sub menu (lihat, cari, kelompok) punya template tampilan seragam biar konsisten.
Bukankah Filter Kategori berisikan opsi pilihan: - Karbohidrat, Protein Hewani, Protein Nabati, Sayur-sayuran, Buah-buahan.
Atau menurut kamu pilihan kategori apa saja yang cocok dan ideal di sini unruk membuat resep masakan nantinya
Kalau tujuan akhirnya adalah membuat resep masakan, kategori bahan makanan memang sebaiknya disusun berdasarkan fungsi gizi dan peran dalam masakan, supaya nanti saat sistem menyusun resep, pemilihan bahan jadi lebih terarah.
Kategori yang kamu sebutkan sudah bagus, tapi bisa sedikit disempurnakan.
Berikut saran kategori ideal yang lebih lengkap:
1. Karbohidrat
- Nasi, mie, roti, jagung, singkong, kentang, ubi, dll.
- Sumber energi utama tubuh.
2. Protein Hewani
- Daging merah (sapi, kambing), unggas (ayam, bebek), ikan, seafood, telur.
- Untuk pertumbuhan, perbaikan jaringan, dan gizi otak.
3. Protein Nabati
- Tahu, tempe, kacang-kacangan, edamame, oncom.
- Alternatif protein untuk vegetarian atau pengganti hewani.
4. Sayur-sayuran
- Bayam, kangkung, wortel, brokoli, kol, buncis, labu, dll.
- Sumber vitamin, mineral, dan serat.
5. Buah-buahan
- Jeruk, pisang, apel, pepaya, semangka, mangga, dll.
- Sumber vitamin, mineral, antioksidan.
6. Bumbu & Rempah
- Bawang merah, bawang putih, jahe, kunyit, cabai, ketumbar, garam, gula, dll.
- Memberi rasa khas pada masakan.
7. Produk Olahan & Tambahan
- Susu, keju, yogurt, mentega, minyak, kecap, saus.
- Untuk memperkaya rasa atau tekstur.
Kalau nanti di sub menu Kelompok Bahan Makanan ini kategori dibuat seperti di atas, maka saat membuat resep:
- Sistem bisa mengambil 1 bahan karbohidrat + 1 protein + 1-2 sayur + bumbu dasar → otomatis menghasilkan komposisi menu seimbang.
- Bisa dibuat filter untuk masakan vegetarian (tanpa kategori protein hewani).
- Bisa dibuat filter rendah kalori, tinggi protein, atau sesuai diet tertentu.
Kalau mau, aku bisa langsung buatkan fitur Filter Kategori ini di sub_menu_kelompokkan_bahan.py supaya pilihan kategori ini muncul di dropdown dan terhubung ke database.
mau, kamu buatkan fitur Filter Kategori ini di sub_menu_kelompokkan_bahan.py.
Baik, aku akan buatkan sub_menu_kelompokkan_bahan.py lengkap dengan fitur Filter Kategori yang terhubung ke database.
Fitur ini akan:
- Menampilkan dropdown pilihan kategori (sesuai daftar yang sudah kita sepakati).
- Menampilkan daftar bahan makanan yang masuk ke kategori itu.
- Memungkinkan menambahkan atau mengedit kategori bahan langsung dari database.
Berikut kode lengkapnya:
# sub_menu_kelompokkan_bahan.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
import os
DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_bahan_makanan.db"
KATEGORI_LIST = [
"Karbohidrat",
"Protein Hewani",
"Protein Nabati",
"Sayur-sayuran",
"Buah-buahan",
"Bumbu & Rempah",
"Produk Olahan & Tambahan"
]
def buat_tabel_kategori():
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
# Tambahkan kolom kategori jika belum ada
c.execute("""
CREATE TABLE IF NOT EXISTS bahan_makanan (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT NOT NULL,
kalori INTEGER,
protein REAL,
lemak REAL,
karbohidrat REAL,
vitamin REAL,
kategori TEXT
)
""")
conn.commit()
conn.close()
def ambil_data_kategori(kategori):
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
if kategori == "Semua":
c.execute("SELECT nama, kalori, protein, lemak, karbohidrat, vitamin, kategori FROM bahan_makanan")
else:
c.execute("SELECT nama, kalori, protein, lemak, karbohidrat, vitamin, kategori FROM bahan_makanan WHERE kategori=?", (kategori,))
hasil = c.fetchall()
conn.close()
return hasil
def show_sub_menu_kelompokkan_bahan(root_frame):
from menu_data_bahan_makanan import show_menu_data_bahan_makanan
buat_tabel_kategori()
for widget in root_frame.winfo_children():
widget.destroy()
# Frame utama
main_frame = tk.Frame(root_frame)
main_frame.pack(fill="both", expand=True)
# Judul
tk.Label(main_frame, text="Kelompokkan Bahan Makanan", font=("Arial", 18, "bold"),
bg="green", fg="white", pady=10).pack(fill="x")
# Frame filter
filter_frame = tk.Frame(main_frame)
filter_frame.pack(fill="x", pady=10)
tk.Label(filter_frame, text="Pilih Kategori:", font=("Arial", 12)).pack(side="left", padx=5)
kategori_var = tk.StringVar(value="Semua")
kategori_dropdown = ttk.Combobox(filter_frame, textvariable=kategori_var, values=["Semua"] + KATEGORI_LIST, state="readonly", width=30)
kategori_dropdown.pack(side="left", padx=5)
# Tombol filter
def update_tabel():
for i in tree.get_children():
tree.delete(i)
data = ambil_data_kategori(kategori_var.get())
for item in data:
tree.insert("", "end", values=item)
tk.Button(filter_frame, text="Tampilkan", command=update_tabel, bg="#4CAF50", fg="white", font=("Arial", 12)).pack(side="left", padx=5)
# Tabel
kolom = ("Nama", "Kalori", "Protein", "Lemak", "Karbohidrat", "Vitamin", "Kategori")
tree = ttk.Treeview(main_frame, columns=kolom, show="headings")
for col in kolom:
tree.heading(col, text=col)
tree.column(col, width=150, anchor="center")
tree.pack(fill="both", expand=True, padx=10, pady=10)
# Isi tabel awal
update_tabel()
# Tombol kembali
tk.Button(main_frame, text="Kembali ke Menu Data Bahan Makanan", font=("Arial", 14),
bg="gray", fg="white", width=35, height=2,
command=lambda: show_menu_data_bahan_makanan(root_frame)).pack(pady=10)
📌 Penjelasan Fitur:
- Dropdown Filter → berisi semua kategori dan opsi “Semua”.
- Tabel → menampilkan daftar bahan sesuai kategori yang dipilih.
- Koneksi Database → kolom
kategorisudah ditambahkan di tabelbahan_makanan. - Fungsi
update_tabel()→ memuat ulang data berdasarkan pilihan dropdown.
Kalau mau lebih interaktif, nanti kita bisa tambahkan tombol "Ubah Kategori Bahan" supaya user bisa memindahkan bahan ke kategori lain langsung dari sini.
Kalau kamu mau, aku bisa lanjut buatkan versi dengan fitur tambah/edit kategori bahan sekarang.
Itu akan bikin menu ini benar-benar powerful untuk pembuatan resep.
Tolong buat posisi tombol "Tampilkan" berada di baris setelah "Pilih Kategori" dengan format "Center" (tombol berada di sebelum tabel), Tambahkan tombol "Tambah Bahan" di kategori ini dan posisinya tombol berada setelah tabel (sebelum tombol "Kembali"). Tambahkan tombol "Hapus Bahan" di kategori ini dan posisinya tombol berada di samping tombol "Tambah Bahan" (sebelum tombol "Kembali"). Dan berikan contoh atau isi tabel awal, dan pada tabel buat kolom "kategori" berada di urutan setelah kolom "nama"
Oke, saya revisikan sub_menu_kelompokkan_bahan.py sesuai permintaan:
- Tombol "Tampilkan" berada di baris terpisah, center tepat sebelum tabel.
- Urutan kolom: Nama, Kategori, Kalori, Protein, Lemak, Karbohidrat, Vitamin.
- Ada data contoh bawaan jika database masih kosong.
- saya tambahkan tombol "Tambah Bahan" di bawah tabel, sebelum tombol "Kembali", lengkap dengan fungsi pop-up input agar user bisa langsung menambahkan bahan sesuai kategori yang sedang dipilih.
- saya tambahkan tombol "Hapus Bahan" di samping tombol "Tambah Bahan", posisinya tetap di bawah tabel dan sebelum tombol "Kembali".
Tombol ini akan menghapus data bahan yang dipilih di tabel.
Berikut kode revisinya:
# sub_menu_kelompokkan_bahan.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_bahan_makanan.db"
KATEGORI_LIST = [
"Karbohidrat",
"Protein Hewani",
"Protein Nabati",
"Sayur-sayuran",
"Buah-buahan",
"Bumbu & Rempah",
"Produk Olahan & Tambahan"
]
def buat_tabel_kategori():
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS bahan_makanan (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT NOT NULL,
kategori TEXT,
kalori INTEGER,
protein REAL,
lemak REAL,
karbohidrat REAL,
vitamin REAL
)
""")
conn.commit()
c.execute("SELECT COUNT(*) FROM bahan_makanan")
if c.fetchone()[0] == 0:
data_awal = [
("Nasi Putih", "Karbohidrat", 175, 3.5, 0.3, 40, 0),
("Ayam", "Protein Hewani", 239, 27, 14, 0, 0),
("Telur", "Protein Hewani", 155, 13, 11, 1.1, 0),
("Wortel", "Sayur-sayuran", 41, 0.9, 0.2, 10, 0),
("Pisang", "Buah-buahan", 89, 1.1, 0.3, 23, 0)
]
c.executemany("INSERT INTO bahan_makanan (nama, kategori, kalori, protein, lemak, karbohidrat, vitamin) VALUES (?, ?, ?, ?, ?, ?, ?)", data_awal)
conn.commit()
conn.close()
def ambil_data_kategori(kategori):
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
if kategori == "Semua":
c.execute("SELECT id, nama, kategori, kalori, protein, lemak, karbohidrat, vitamin FROM bahan_makanan")
else:
c.execute("SELECT id, nama, kategori, kalori, protein, lemak, karbohidrat, vitamin FROM bahan_makanan WHERE kategori=?", (kategori,))
hasil = c.fetchall()
conn.close()
return hasil
def tambah_bahan_popup(kategori_awal, refresh_callback):
popup = tk.Toplevel()
popup.title("Tambah Bahan")
popup.geometry("400x400")
tk.Label(popup, text="Nama Bahan:").pack(pady=5)
entry_nama = tk.Entry(popup, width=30)
entry_nama.pack()
tk.Label(popup, text="Kategori:").pack(pady=5)
kategori_var = tk.StringVar(value=kategori_awal if kategori_awal != "Semua" else KATEGORI_LIST[0])
ttk.Combobox(popup, textvariable=kategori_var, values=KATEGORI_LIST, state="readonly").pack()
tk.Label(popup, text="Kalori (kkal):").pack(pady=5)
entry_kalori = tk.Entry(popup, width=30)
entry_kalori.pack()
tk.Label(popup, text="Protein (g):").pack(pady=5)
entry_protein = tk.Entry(popup, width=30)
entry_protein.pack()
tk.Label(popup, text="Lemak (g):").pack(pady=5)
entry_lemak = tk.Entry(popup, width=30)
entry_lemak.pack()
tk.Label(popup, text="Karbohidrat (g):").pack(pady=5)
entry_karbo = tk.Entry(popup, width=30)
entry_karbo.pack()
tk.Label(popup, text="Vitamin (mg):").pack(pady=5)
entry_vitamin = tk.Entry(popup, width=30)
entry_vitamin.pack()
def simpan():
nama = entry_nama.get().strip()
kategori = kategori_var.get()
try:
kalori = float(entry_kalori.get())
protein = float(entry_protein.get())
lemak = float(entry_lemak.get())
karbo = float(entry_karbo.get())
vitamin = float(entry_vitamin.get())
except ValueError:
messagebox.showerror("Error", "Pastikan semua nilai gizi diisi dengan angka.")
return
if not nama:
messagebox.showerror("Error", "Nama bahan tidak boleh kosong.")
return
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("INSERT INTO bahan_makanan (nama, kategori, kalori, protein, lemak, karbohidrat, vitamin) VALUES (?, ?, ?, ?, ?, ?, ?)",
(nama, kategori, kalori, protein, lemak, karbo, vitamin))
conn.commit()
conn.close()
messagebox.showinfo("Sukses", f"{nama} berhasil ditambahkan.")
popup.destroy()
refresh_callback()
tk.Button(popup, text="Simpan", command=simpan, bg="#4CAF50", fg="white").pack(pady=10)
def hapus_bahan(tree, refresh_callback):
selected_item = tree.selection()
if not selected_item:
messagebox.showwarning("Peringatan", "Pilih bahan yang ingin dihapus.")
return
confirm = messagebox.askyesno("Konfirmasi", "Yakin ingin menghapus bahan terpilih?")
if not confirm:
return
item_id = tree.item(selected_item[0], "values")[0] # ID ada di kolom pertama
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("DELETE FROM bahan_makanan WHERE id=?", (item_id,))
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Bahan berhasil dihapus.")
refresh_callback()
def show_sub_menu_kelompokkan_bahan(root_frame):
from menu_data_bahan_makanan import show_menu_data_bahan_makanan
buat_tabel_kategori()
for widget in root_frame.winfo_children():
widget.destroy()
main_frame = tk.Frame(root_frame)
main_frame.pack(fill="both", expand=True)
tk.Label(main_frame, text="Kelompokkan Bahan Makanan", font=("Arial", 18, "bold"),
bg="green", fg="white", pady=10).pack(fill="x")
filter_frame = tk.Frame(main_frame)
filter_frame.pack(fill="x", pady=10)
tk.Label(filter_frame, text="Pilih Kategori:", font=("Arial", 12)).pack(side="left", padx=5)
kategori_var = tk.StringVar(value="Semua")
kategori_dropdown = ttk.Combobox(filter_frame, textvariable=kategori_var, values=["Semua"] + KATEGORI_LIST, state="readonly", width=30)
kategori_dropdown.pack(side="left", padx=5)
tk.Button(main_frame, text="Tampilkan", command=lambda: update_tabel(), bg="#4CAF50", fg="white", font=("Arial", 12)).pack(pady=5)
kolom = ("ID", "Nama", "Kategori", "Kalori", "Protein", "Lemak", "Karbohidrat", "Vitamin")
tree = ttk.Treeview(main_frame, columns=kolom, show="headings")
for col in kolom:
tree.heading(col, text=col)
tree.column(col, width=150, anchor="center")
tree.pack(fill="both", expand=True, padx=10, pady=10)
def update_tabel():
for i in tree.get_children():
tree.delete(i)
data = ambil_data_kategori(kategori_var.get())
for item in data:
tree.insert("", "end", values=item)
update_tabel()
# Frame tombol Tambah & Hapus
button_action_frame = tk.Frame(main_frame)
button_action_frame.pack(pady=5)
tk.Button(button_action_frame, text="Tambah Bahan", font=("Arial", 12),
bg="#2196F3", fg="white", width=20,
command=lambda: tambah_bahan_popup(kategori_var.get(), update_tabel)).pack(side="left", padx=5)
tk.Button(button_action_frame, text="Hapus Bahan", font=("Arial", 12),
bg="red", fg="white", width=20,
command=lambda: hapus_bahan(tree, update_tabel)).pack(side="left", padx=5)
# Tombol kembali
tk.Button(main_frame, text="Kembali ke Menu Data Bahan Makanan", font=("Arial", 14),
bg="gray", fg="white", width=35, height=2,
command=lambda: show_menu_data_bahan_makanan(root_frame)).pack(pady=10)
🔹 Perubahan dari versi sebelumnya:
1. Urutan kolom tabel → Nama, Kategori, lalu gizi.
2. Tombol “Tampilkan” → pindah ke baris baru, center sebelum tabel.
3. Data contoh bawaan → otomatis masuk jika database kosong.
4. Lebar kolom sama & data center supaya rapi.
5. Tambah tombol "Tambah Bahan" setelah tabel dan sebelum tombol kembali.
6. Pop-up input memungkinkan user menambahkan bahan sesuai kategori yang sedang dipilih.
7. Jika kategori dropdown di pop-up dipilih, akan masuk ke DB dan tabel otomatis refresh.
8. Menambahkan fungsi hapus_bahan() untuk menghapus data dari database berdasarkan ID.
9. Mengubah kolom tabel agar ID bahan ada di kolom pertama (agar tahu data mana yang dihapus).
10. Menambahkan frame horizontal di bawah tabel untuk menempatkan tombol Tambah Bahan dan Hapus Bahan berdampingan.
11. Memastikan tabel otomatis refresh setelah menambah atau menghapus bahan.
Kalau mau, saya bisa bikin ID ini tidak ditampilkan ke user (disembunyikan dari tampilan tapi tetap ada di data) supaya tabel kelihatan lebih rapi.
Mau saya buat seperti itu?
Sampai saat ini perubahan, penyempurnaan, dan tambahan file pada applikasi SmartNutriChefAI adalah:
1. menu_data_bahan_makanan.py
Kode programnya adalah berikut:
# Menu Data Bahan Makanan
import tkinter as tk
from DataBahanMakanan import (
sub_menu_lihat_daftar_bahan as lihat,
sub_menu_cari_data_gizi as cari,
sub_menu_kelompokkan_bahan as kelompok
)
def show_menu_data_bahan_makanan(root_frame):
# Hapus widget lama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
tk.Label(root_frame, text="Data Bahan Makanan",
font=("Arial", 18, "bold"),
bg="green", fg="white", pady=10).pack(pady=20)
# Daftar tombol sub menu
menu_items = [
("Lihat Daftar Bahan", lihat.show_sub_menu_lihat_daftar_bahan),
("Cari Data Gizi", cari.show_sub_menu_cari_data_gizi),
("Kelompokkan Bahan", kelompok.show_sub_menu_kelompokkan_bahan),
]
for text, cmd in menu_items:
tk.Button(root_frame, text=text, font=("Arial", 14),
bg="#4CAF50", fg="white", width=25, height=2,
command=lambda c=cmd: c(root_frame)).pack(pady=10)
# Tombol kembali
from menu_utama import tampilkan_halaman_utama
tk.Button(root_frame, text="Kembali Ke Menu Utama",
font=("Arial", 14),
bg="gray", fg="white", width=25, height=2,
command=lambda: tampilkan_halaman_utama(root_frame)).pack(pady=20)
2. sub_menu_kelompokkan_bahan.py
Kode programnya adalah berikut:
# sub_menu_kelompokkan_bahan.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3
DATABASE_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/data_bahan_makanan.db"
KATEGORI_LIST = [
"Karbohidrat",
"Protein Hewani",
"Protein Nabati",
"Sayur-sayuran",
"Buah-buahan",
"Bumbu & Rempah",
"Produk Olahan & Tambahan"
]
def buat_tabel_kategori():
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS bahan_makanan (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nama TEXT NOT NULL,
kategori TEXT,
kalori INTEGER,
protein REAL,
lemak REAL,
karbohidrat REAL,
vitamin REAL
)
""")
conn.commit()
c.execute("SELECT COUNT(*) FROM bahan_makanan")
if c.fetchone()[0] == 0:
data_awal = [
("Nasi Putih", "Karbohidrat", 175, 3.5, 0.3, 40, 0),
("Ayam", "Protein Hewani", 239, 27, 14, 0, 0),
("Telur", "Protein Hewani", 155, 13, 11, 1.1, 0),
("Wortel", "Sayur-sayuran", 41, 0.9, 0.2, 10, 0),
("Pisang", "Buah-buahan", 89, 1.1, 0.3, 23, 0)
]
c.executemany("INSERT INTO bahan_makanan (nama, kategori, kalori, protein, lemak, karbohidrat, vitamin) VALUES (?, ?, ?, ?, ?, ?, ?)", data_awal)
conn.commit()
conn.close()
def ambil_data_kategori(kategori):
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
if kategori == "Semua":
c.execute("SELECT id, nama, kategori, kalori, protein, lemak, karbohidrat, vitamin FROM bahan_makanan")
else:
c.execute("SELECT id, nama, kategori, kalori, protein, lemak, karbohidrat, vitamin FROM bahan_makanan WHERE kategori=?", (kategori,))
hasil = c.fetchall()
conn.close()
return hasil
def tambah_bahan_popup(kategori_awal, refresh_callback):
popup = tk.Toplevel()
popup.title("Tambah Bahan")
popup.geometry("450x750")
tk.Label(popup, text="Nama Bahan:").pack(pady=5)
entry_nama = tk.Entry(popup, width=30)
entry_nama.pack()
tk.Label(popup, text="Kategori:").pack(pady=5)
kategori_var = tk.StringVar(value=kategori_awal if kategori_awal != "Semua" else KATEGORI_LIST[0])
ttk.Combobox(popup, textvariable=kategori_var, values=KATEGORI_LIST, state="readonly").pack()
tk.Label(popup, text="Kalori (kkal):").pack(pady=5)
entry_kalori = tk.Entry(popup, width=28)
entry_kalori.pack()
tk.Label(popup, text="Protein (g):").pack(pady=5)
entry_protein = tk.Entry(popup, width=28)
entry_protein.pack()
tk.Label(popup, text="Lemak (g):").pack(pady=5)
entry_lemak = tk.Entry(popup, width=28)
entry_lemak.pack()
tk.Label(popup, text="Karbohidrat (g):").pack(pady=5)
entry_karbo = tk.Entry(popup, width=28)
entry_karbo.pack()
tk.Label(popup, text="Vitamin (mg):").pack(pady=5)
entry_vitamin = tk.Entry(popup, width=28)
entry_vitamin.pack()
def simpan():
nama = entry_nama.get().strip()
kategori = kategori_var.get()
try:
kalori = float(entry_kalori.get())
protein = float(entry_protein.get())
lemak = float(entry_lemak.get())
karbo = float(entry_karbo.get())
vitamin = float(entry_vitamin.get())
except ValueError:
messagebox.showerror("Error", "Pastikan semua nilai gizi diisi dengan angka.")
return
if not nama:
messagebox.showerror("Error", "Nama bahan tidak boleh kosong.")
return
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("INSERT INTO bahan_makanan (nama, kategori, kalori, protein, lemak, karbohidrat, vitamin) VALUES (?, ?, ?, ?, ?, ?, ?)",
(nama, kategori, kalori, protein, lemak, karbo, vitamin))
conn.commit()
conn.close()
messagebox.showinfo("Sukses", f"{nama} berhasil ditambahkan.")
popup.destroy()
refresh_callback()
tk.Button(popup, text="Simpan", command=simpan, bg="#4CAF50", font=("Arial", 10, "bold"), fg="white").pack(pady=10)
def hapus_bahan(tree, refresh_callback):
selected_item = tree.selection()
if not selected_item:
messagebox.showwarning("Peringatan", "Pilih bahan yang ingin dihapus.")
return
confirm = messagebox.askyesno("Konfirmasi", "Yakin ingin menghapus bahan terpilih?")
if not confirm:
return
item_id = tree.item(selected_item[0], "values")[0] # ID ada di kolom pertama
conn = sqlite3.connect(DATABASE_PATH)
c = conn.cursor()
c.execute("DELETE FROM bahan_makanan WHERE id=?", (item_id,))
conn.commit()
conn.close()
messagebox.showinfo("Sukses", "Bahan berhasil dihapus.")
refresh_callback()
def show_sub_menu_kelompokkan_bahan(root_frame):
from menu_data_bahan_makanan import show_menu_data_bahan_makanan
buat_tabel_kategori()
for widget in root_frame.winfo_children():
widget.destroy()
main_frame = tk.Frame(root_frame)
main_frame.pack(fill="both", expand=True)
tk.Label(main_frame, text="Kelompok Bahan Makanan", font=("Arial", 16, "bold"),
bg="green", fg="white", pady=10).pack(fill="x")
filter_frame = tk.Frame(main_frame)
filter_frame.pack(fill="x", pady=10)
tk.Label(filter_frame, text="Pilih Kategori:", font=("Arial", 12)).pack(side="left", padx=5)
kategori_var = tk.StringVar(value="Semua")
kategori_dropdown = ttk.Combobox(filter_frame, textvariable=kategori_var, values=["Semua"] + KATEGORI_LIST, state="readonly", width=28)
kategori_dropdown.pack(side="left", padx=5)
tk.Button(main_frame, text="Tampilkan", command=lambda: update_tabel(), bg="#4CAF50", fg="white", font=("Arial", 12)).pack(pady=5)
kolom = ("ID", "Nama", "Kategori", "Kalori", "Protein", "Lemak", "Karbohidrat", "Vitamin")
tree = ttk.Treeview(main_frame, columns=kolom, show="headings")
for col in kolom:
tree.heading(col, text=col)
tree.column(col, width=150, anchor="center")
tree.pack(fill="both", expand=True, padx=10, pady=10)
def update_tabel():
for i in tree.get_children():
tree.delete(i)
data = ambil_data_kategori(kategori_var.get())
for item in data:
tree.insert("", "end", values=item)
update_tabel()
# Frame tombol Tambah & Hapus
button_action_frame = tk.Frame(main_frame)
button_action_frame.pack(pady=5)
tk.Button(button_action_frame, text="Tambah Bahan", font=("Arial", 11),
bg="#2196F3", fg="white", width=15,
command=lambda: tambah_bahan_popup(kategori_var.get(), update_tabel)).pack(side="left", padx=5)
tk.Button(button_action_frame, text="Hapus Bahan", font=("Arial", 11),
bg="red", fg="white", width=15,
command=lambda: hapus_bahan(tree, update_tabel)).pack(side="left", padx=5)
# Tombol kembali
tk.Button(main_frame, text="Kembali", font=("Arial", 16),
bg="gray", fg="white", width=25, height=2,
command=lambda: show_menu_data_bahan_makanan(root_frame)).pack(pady=10)
Bersambung Ke: Konten Sub Menu "Analisis Kombinasi Bahan"
Comments
Post a Comment