Isi Menu "Riwayat Aktivitas" (Bagian 2)
Isi Menu "Riwayat Aktivitas" (Bagian 2)
(Konten Sub Menu "Lihat Riwayat Penggunaan ")
Sekarang kita masuk ke Menu " Riwayat Aktivitas" , kita lanjut membuat konten Sub Menu **"**Lihat Riwayat Penggunaan" ****
Saya sudah punya file menu_riwayat_aktivitas.py berikut ini:
Menu Riwayat Aktivitas
import tkinter as tk
from tkinter import ttk
from RiwayatAktivitas.sub_menu_riwayat_penggunaan import sub_menu_riwayat_penggunaan
def show_menu_riwayat_aktivitas(root_frame):
from menu_utama import tampilkan_halaman_utama # import lokal agar aman
# Bersihkan frame utama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
label_title = tk.Label(
root_frame,
text="Riwayat Aktivitas",
font=("Arial", 16, "bold"),
bg="light grey"
)
label_title.pack(pady=10)
# Fungsi wrapper agar frame dibersihkan sebelum memanggil sub menu
def open_submenu(func):
for widget in root_frame.winfo_children():
widget.destroy()
func(root_frame, lambda: show_menu_riwayat_aktivitas(root_frame))
# kirim go_back → kembali ke menu riwayat
# Submenu tombol
submenus = [
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan)),
]
for text, command in submenus:
btn = tk.Button(
root_frame,
text=text,
font=("Arial", 10),
width=40,
height=2,
bg="light blue",
fg="dark green",
command=command
)
btn.pack(pady=5)
# Tombol kembali ke menu utama
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)
Dan juga punya file sub_menu_riwayat_penggunaan.py berikut ini:
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
from datetime import datetime
Lokasi database
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
"""Inisialisasi database & tabel jika belum ada"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
ket TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, nama_file="", keterangan=""):
"""Catat aktivitas ke database SQLite"""
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("INSERT INTO riwayat (waktu, jenis, file, ket) VALUES (?, ?, ?, ?)",
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, nama_file, keterangan))
conn.commit()
conn.close()
def sub_menu_riwayat_penggunaan(root_frame, go_back):
"""Tampilan sub menu Lihat Riwayat Penggunaan"""
init_db()
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
# --- Judul ---
tk.Label(frame_content, text="Riwayat Penggunaan",
font=("Arial", 14, "bold")).pack(pady=10)
# --- Tabel Treeview ---
columns = ("waktu", "jenis", "file", "ket")
tree = ttk.Treeview(frame_content, columns=columns, show="headings", height=15)
tree.pack(fill="both", expand=True, padx=10, pady=6)
for col, text in zip(columns, ["Waktu", "Jenis Aktivitas", "Nama File/Resep", "Keterangan"]):
tree.heading(col, text=text)
tree.column(col, anchor="w", width=150)
# --- Tombol Aksi (baris 1) ---
action_frame = tk.Frame(frame_content)
action_frame.pack(fill="x", pady=8)
def load_data():
tree.delete(*tree.get_children())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id DESC")
for row in cur.fetchall():
tree.insert("", "end", values=row)
conn.close()
def export_riwayat():
# Export ke file teks sederhana
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id")
rows = cur.fetchall()
conn.close()
if rows:
export_path = os.path.join(os.path.dirname(DB_PATH), "riwayat_export.txt")
with open(export_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(f"{r[0]} | {r[1]} | {r[2]} | {r[3]}\n")
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
ttk.Button(action_frame, text="Refresh", command=load_data).pack(side="left", padx=6)
ttk.Button(action_frame, text="Export Riwayat", command=export_riwayat).pack(side="left", padx=6)
ttk.Button(action_frame, text="Hapus Riwayat", command=hapus_riwayat).pack(side="left", padx=6)
# --- Tombol kembali di baris baru ---
back_frame = tk.Frame(frame_content)
back_frame.pack(fill="x", pady=8)
ttk.Button(back_frame, text="⬅ Kembali", command=go_back).pack(pady=5)
# --- Load awal ---
load_data()
return frame_content
Mantap, kode yang sudah kamu buat untuk menu_riwayat_aktivitas.py dan sub_menu_riwayat_penggunaan.py sebenarnya sudah cukup bagus 👍
Tapi saya perhatikan ada dua hal yang perlu diperbaiki/ditingkatkan supaya lebih lancar:
🔑 Masalah yang sering muncul
-
Tombol “Kembali” di
sub_menu_riwayat_penggunaanharus benar-benar kembali ke menu riwayat aktivitas.
→ Saat ini sudah oke, karena kamu passinggo_backdarimenu_riwayat_aktivitas.py. -
Integrasi pencatatan aktivitas (fungsi
catat_aktivitas) belum dipakai di tempat lain. Jadi riwayat masih kosong.
✅ Perbaikan menu_riwayat_aktivitas.py
Saya tambahkan pemanggilan catat_aktivitas setiap kali submenu dibuka, biar ada log otomatis.
# Menu Riwayat Aktivitas
import tkinter as tk
from tkinter import ttk
from RiwayatAktivitas.sub_menu_riwayat_penggunaan import sub_menu_riwayat_penggunaan, catat_aktivitas
def show_menu_riwayat_aktivitas(root_frame):
from menu_utama import tampilkan_halaman_utama # import lokal agar aman
# Bersihkan frame utama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
label_title = tk.Label(
root_frame,
text="Riwayat Aktivitas",
font=("Arial", 16, "bold"),
bg="light grey"
)
label_title.pack(pady=10)
# Fungsi wrapper agar frame dibersihkan sebelum memanggil sub menu
def open_submenu(func, jenis="Submenu"):
for widget in root_frame.winfo_children():
widget.destroy()
# Catat ke riwayat
catat_aktivitas(jenis=jenis, nama_file=func.__name__, keterangan="Dibuka oleh user")
func(root_frame, lambda: show_menu_riwayat_aktivitas(root_frame))
# kirim go_back → kembali ke menu riwayat
# Submenu tombol
submenus = [
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
]
for text, command in submenus:
btn = tk.Button(
root_frame,
text=text,
font=("Arial", 10),
width=40,
height=2,
bg="light blue",
fg="dark green",
command=command
)
btn.pack(pady=5)
# Tombol kembali ke menu utama
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 kecil sub_menu_riwayat_penggunaan.py
Tambahkan catatan aktivitas juga saat export dan hapus, supaya tercatat di DB.
def export_riwayat():
...
if rows:
...
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
catat_aktivitas("Export", export_path, "Export riwayat ke file txt")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
catat_aktivitas("Hapus", "", "Semua riwayat dihapus")
🔎 Hasil yang didapat
- Setiap kali user masuk ke submenu “Lihat Riwayat Penggunaan”, otomatis tercatat di DB.
- Saat user Export atau Hapus riwayat, juga ikut tercatat.
- Jadi kalau nanti buka menu riwayat, tabel tidak kosong lagi (sudah ada log).
Mau saya bikinkan juga fitur pencarian/filter riwayat (misalnya berdasarkan tanggal atau jenis aktivitas), supaya tabel Treeview lebih gampang dipakai kalau datanya sudah banyak?
Kita gunakan file sub_menu_riwayat_penggunaan.py yang ini saja:
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
from datetime import datetime
Lokasi database
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
"""Inisialisasi database & tabel jika belum ada"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
ket TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, nama_file="", keterangan=""):
"""Catat aktivitas ke database SQLite"""
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("INSERT INTO riwayat (waktu, jenis, file, ket) VALUES (?, ?, ?, ?)",
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, nama_file, keterangan))
conn.commit()
conn.close()
def sub_menu_riwayat_penggunaan(root_frame, go_back):
"""Tampilan sub menu Lihat Riwayat Penggunaan"""
init_db()
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
# --- Judul ---
tk.Label(frame_content, text="Riwayat Penggunaan",
font=("Arial", 14, "bold")).pack(pady=10)
# --- Tabel Treeview ---
columns = ("waktu", "jenis", "file", "ket")
tree = ttk.Treeview(frame_content, columns=columns, show="headings", height=15)
tree.pack(fill="both", expand=True, padx=10, pady=6)
for col, text in zip(columns, ["Waktu", "Jenis Aktivitas", "Nama File/Resep", "Keterangan"]):
tree.heading(col, text=text)
tree.column(col, anchor="w", width=150)
# --- Tombol Aksi (baris 1) ---
action_frame = tk.Frame(frame_content)
action_frame.pack(fill="x", pady=8)
def load_data():
tree.delete(*tree.get_children())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id DESC")
for row in cur.fetchall():
tree.insert("", "end", values=row)
conn.close()
def export_riwayat():
# Export ke file teks sederhana
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id")
rows = cur.fetchall()
conn.close()
if rows:
export_path = os.path.join(os.path.dirname(DB_PATH), "riwayat_export.txt")
with open(export_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(f"{r[0]} | {r[1]} | {r[2]} | {r[3]}\n")
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
ttk.Button(action_frame, text="Refresh", command=load_data).pack(side="left", padx=6)
ttk.Button(action_frame, text="Export Riwayat", command=export_riwayat).pack(side="left", padx=6)
ttk.Button(action_frame, text="Hapus Riwayat", command=hapus_riwayat).pack(side="left", padx=6)
# --- Tombol kembali di baris baru ---
back_frame = tk.Frame(frame_content)
back_frame.pack(fill="x", pady=8)
ttk.Button(back_frame, text="⬅ Kembali", command=go_back).pack(pady=5)
# --- Load awal ---
load_data()
return frame_content
Dan juga file riwayat_utils.py yang ini:
import sqlite3, os
from datetime import datetime
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat_aktivitas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
keterangan TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, file="", ket=""):
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO riwayat_aktivitas (waktu, jenis, file, keterangan)
VALUES (?, ?, ?, ?)
""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, file, ket))
conn.commit()
conn.close()
Oke 👍, sekarang sudah jelas: kamu punya dua sumber pencatat riwayat:
sub_menu_riwayat_penggunaan.py→ tabel bernama riwayatriwayat_utils.py→ tabel bernama riwayat_aktivitas
Kalau dibiarkan gini, akan bikin bingung, karena datanya nyebar ke 2 tabel yang berbeda.
Saran saya: satukan supaya seluruh aplikasi hanya pakai satu tabel dan satu fungsi utilitas.
🔧 Rekomendasi
Kita pakai file riwayat_utils.py sebagai pusat (lebih bagus karena modular).
Berarti:
- Di
riwayat_utils.py, tabel sebaiknya riwayat saja (bukan riwayat_aktivitas), supaya sinkron dengan sub_menu. - Semua fungsi
init_db()dancatat_aktivitas()dipanggil daririwayat_utils, bukan bikin ulang disub_menu_riwayat_penggunaan.py.
✅ Revisi riwayat_utils.py
# riwayat_utils.py
import sqlite3, os
from datetime import datetime
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
"""Inisialisasi database & tabel jika belum ada"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
ket TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, file="", ket=""):
"""Catat aktivitas ke database SQLite"""
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO riwayat (waktu, jenis, file, ket)
VALUES (?, ?, ?, ?)
""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, file, ket))
conn.commit()
conn.close()
✅ Revisi sub_menu_riwayat_penggunaan.py
Supaya konsisten, tinggal impor fungsi dari riwayat_utils:
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
from RiwayatAktivitas.riwayat_utils import DB_PATH, init_db, catat_aktivitas
def sub_menu_riwayat_penggunaan(root_frame, go_back):
"""Tampilan sub menu Lihat Riwayat Penggunaan"""
init_db()
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
# --- Judul ---
tk.Label(frame_content, text="Riwayat Penggunaan",
font=("Arial", 14, "bold")).pack(pady=10)
# --- Tabel Treeview ---
columns = ("waktu", "jenis", "file", "ket")
tree = ttk.Treeview(frame_content, columns=columns, show="headings", height=15)
tree.pack(fill="both", expand=True, padx=10, pady=6)
for col, text in zip(columns, ["Waktu", "Jenis Aktivitas", "Nama File/Resep", "Keterangan"]):
tree.heading(col, text=text)
tree.column(col, anchor="w", width=150)
# --- Tombol Aksi ---
action_frame = tk.Frame(frame_content)
action_frame.pack(fill="x", pady=8)
def load_data():
tree.delete(*tree.get_children())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id DESC")
for row in cur.fetchall():
tree.insert("", "end", values=row)
conn.close()
def export_riwayat():
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id")
rows = cur.fetchall()
conn.close()
if rows:
export_path = os.path.join(os.path.dirname(DB_PATH), "riwayat_export.txt")
with open(export_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(f"{r[0]} | {r[1]} | {r[2]} | {r[3]}\n")
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
catat_aktivitas("Export", export_path, "Export riwayat ke file txt")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
catat_aktivitas("Hapus", "", "Semua riwayat dihapus")
ttk.Button(action_frame, text="Refresh", command=load_data).pack(side="left", padx=6)
ttk.Button(action_frame, text="Export Riwayat", command=export_riwayat).pack(side="left", padx=6)
ttk.Button(action_frame, text="Hapus Riwayat", command=hapus_riwayat).pack(side="left", padx=6)
# --- Tombol kembali ---
back_frame = tk.Frame(frame_content)
back_frame.pack(fill="x", pady=8)
ttk.Button(back_frame, text="⬅ Kembali", command=go_back).pack(pady=5)
# --- Load awal ---
load_data()
return frame_content
🔎 Hasilnya
- Semua fitur (lihat, refresh, export, hapus) pakai tabel riwayat (bukan
riwayat_aktivitas). - Fungsi
catat_aktivitascuma ada diriwayat_utils.py(lebih bersih, modular). - Bisa dipanggil dari fitur lain juga, contoh:
from RiwayatAktivitas.riwayat_utils import catat_aktivitas
catat_aktivitas("Buka Resep", "resep_ayam.txt", "User membuka resep ayam")
Mau saya bikinkan juga hook global supaya setiap kali user membuka submenu/menu apa pun, otomatis tercatat ke riwayat tanpa harus panggil catat_aktivitas manual di tiap file?.
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_riwayat_aktivitas.py", line 35, in
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/storage/emulated/0/AppJadi/SmartNutriChefAI/menu_riwayat_aktivitas.py", line 29, in open_submenu
catat_aktivitas(jenis=jenis, nama_file=func.name, keterangan="Dibuka oleh user")
TypeError: catat_aktivitas() got an unexpected keyword argument 'nama_file'.
Oke, error ini jelas:
TypeError: catat_aktivitas() got an unexpected keyword argument 'nama_file'
Artinya, fungsi catat_aktivitas tidak punya parameter nama_file, tapi kamu memanggilnya dengan nama_file=func.__name__.
Mari kita cek penyebabnya:
1. Pemanggilan di menu_riwayat_aktivitas.py
catat_aktivitas(jenis=jenis, nama_file=func.__name__, keterangan="Dibuka oleh user")
2. Definisi fungsi catat_aktivitas (di file log_aktivitas.py atau sejenisnya)
Kemungkinan besar fungsinya seperti ini:
def catat_aktivitas(jenis, keterangan):
...
atau
def catat_aktivitas(jenis, file, keterangan):
...
✅ Solusi
Ada dua pilihan, tergantung apakah kamu memang ingin menyimpan nama_file di log:
Opsi A: Tambahkan parameter nama_file di definisi catat_aktivitas
Misalnya di log_aktivitas.py:
def catat_aktivitas(jenis, nama_file=None, keterangan=""):
with open("riwayat_aktivitas.log", "a") as f:
f.write(f"[{jenis}] {nama_file or '-'} - {keterangan}\n")
Maka pemanggilanmu di menu_riwayat_aktivitas.py akan valid.
Opsi B: Jangan kirim nama_file, cukup masukin ke keterangan
Kalau sebenarnya kamu tidak butuh kolom khusus nama_file, bisa ubah jadi:
catat_aktivitas(
jenis=jenis,
keterangan=f"Dibuka oleh user (file: {func.__name__})"
)
👉 Jadi pertanyaannya ke kamu:
Apakah nama_file ini memang mau dijadikan kolom/field tersendiri di log (opsi A), atau cukup jadi bagian dari keterangan (opsi B)?
Kalau kamu pilih, nanti saya bikinkan revisi nyata ke file log_aktivitas.py dan menu_riwayat_aktivitas.py.
Mau pilih opsi A (pisah kolom nama_file) atau opsi B (digabung ke keterangan)?
Mau kamu pilih opsi A.
Sip 👍, kalau kita pilih opsi A, berarti kita perlu revisi fungsi catat_aktivitas supaya mendukung parameter tambahan nama_file.
Contoh revisi:
📌 log_aktivitas.py
import datetime
import os
LOG_FILE = "riwayat_aktivitas.log"
def catat_aktivitas(jenis: str, nama_file: str = None, keterangan: str = ""):
"""Catat aktivitas user ke file log"""
waktu = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Pastikan file ada
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) if os.path.dirname(LOG_FILE) else None
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(f"[{waktu}] Jenis: {jenis} | File: {nama_file or '-'} | Keterangan: {keterangan}\n")
📌 menu_riwayat_aktivitas.py
Pemanggilan sekarang sudah valid, jadi tidak error lagi:
catat_aktivitas(
jenis=jenis,
nama_file=func.__name__,
keterangan="Dibuka oleh user"
)
📌 Contoh isi log (riwayat_aktivitas.log)
[2025-09-22 18:45:32] Jenis: SubMenu | File: sub_menu_riwayat_penggunaan | Keterangan: Dibuka oleh user
[2025-09-22 18:46:10] Jenis: SubMenu | File: sub_menu_bahasa_aplikasi | Keterangan: Dibuka oleh user
Mau saya bikinkan juga versi pembacaan log ini di dalam SubMenuRiwayatPenggunaan biar user bisa lihat daftar aktivitas langsung di aplikasi?.
Pakai file riwayat_utils.py:
riwayat_utils.py
import sqlite3, os
from datetime import datetime
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
"""Inisialisasi database & tabel jika belum ada"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
ket TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, file="", ket=""):
"""Catat aktivitas ke database SQLite"""
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO riwayat (waktu, jenis, file, ket)
VALUES (?, ?, ?, ?)
""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, file, ket))
conn.commit()
conn.close()
Ataukaj pkai file log_aktivitas.py:
import datetime
import os
LOG_FILE = "riwayat_aktivitas.log"
def catat_aktivitas(jenis: str, nama_file: str = None, keterangan: str = ""):
"""Catat aktivitas user ke file log"""
waktu = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# Pastikan file ada
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True) if os.path.dirname(LOG_FILE) else None
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(f"[{waktu}] Jenis: {jenis} | File: {nama_file or '-'} | Keterangan: {keterangan}\n")
Kalau pilihannya mau jelas, kita bisa bandingkan:
✅ Opsi 1 – Pakai riwayat_utils.py (SQLite)
- Cocok kalau kamu mau:
- Data tersimpan rapi dalam database.
- Bisa ditampilkan ulang ke Treeview Tkinter dengan query SQL.
- Bisa export, filter, hapus sebagian, dll.
- Sudah ada integrasi dengan
sub_menu_riwayat_penggunaan.py. - Fleksibel untuk ke depan (misalnya pencarian, statistik, grafik penggunaan).
Contoh pemanggilan:
from riwayat_utils import catat_aktivitas
catat_aktivitas(jenis="SubMenu", file="sub_menu_riwayat_penggunaan", ket="Dibuka oleh user")
✅ Opsi 2 – Pakai log_aktivitas.py (File teks .log)
- Cocok kalau kamu mau:
- Cara paling sederhana (sekedar catat di file teks).
- Tidak perlu database SQLite.
- Bisa dibaca pakai text editor.
- Kurang fleksibel kalau nanti ingin ditampilkan dalam tabel GUI (perlu parsing manual dari
.log).
Contoh pemanggilan:
from log_aktivitas import catat_aktivitas
catat_aktivitas(jenis="SubMenu", nama_file="sub_menu_riwayat_penggunaan", keterangan="Dibuka oleh user")
🚀 Rekomendasi saya untuk aplikasi ini:
Lebih baik gunakan riwayat_utils.py (SQLite).
Alasannya: aplikasi kamu sudah ada sub menu “Riwayat Penggunaan” dengan Treeview → langsung cocok dengan database SQLite.
Kalau pakai log_aktivitas.py, nanti kamu harus bikin parser tambahan biar datanya bisa ditampilkan di tabel, jadi kerja dua kali.
Mau saya bikinkan contoh integrasi catat_aktivitas dari riwayat_utils.py langsung ke sub_menu_riwayat_penggunaan.py biar otomatis setiap buka sub menu tercatat di database?
Mau kamu bikinkan contoh integrasi catat_aktivitas dari riwayat_utils.py langsung ke sub_menu_riwayat_penggunaan.py biar otomatis setiap buka sub menu tercatat di database.
Dan kamu bikinkan juga hook global supaya setiap kali user membuka submenu/menu apa pun, otomatis tercatat ke riwayat tanpa harus panggil catat_aktivitas manual di tiap file.
Oke 👍, kita buat 2 hal:
- Integrasi langsung ke
sub_menu_riwayat_penggunaan.py(otomatis catat aktivitas begitu submenu dibuka). - Hook global yang bisa dipakai oleh semua menu/submenu, jadi tidak perlu panggil
catat_aktivitasmanual di tiap file.
1. Integrasi di sub_menu_riwayat_penggunaan.py
Kita cukup panggil catat_aktivitas dari riwayat_utils.py saat fungsi sub_menu_riwayat_penggunaan dipanggil:
# sub_menu_riwayat_penggunaan.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
from datetime import datetime
from riwayat_utils import catat_aktivitas, init_db # <-- Tambah ini
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def sub_menu_riwayat_penggunaan(root_frame, go_back):
"""Tampilan sub menu Lihat Riwayat Penggunaan"""
init_db()
# --- Catat otomatis setiap kali submenu ini dibuka ---
catat_aktivitas(
jenis="SubMenu",
file="sub_menu_riwayat_penggunaan",
ket="Dibuka oleh user"
)
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
# --- Judul ---
tk.Label(frame_content, text="Riwayat Penggunaan",
font=("Arial", 14, "bold")).pack(pady=10)
# --- Tabel Treeview ---
columns = ("waktu", "jenis", "file", "ket")
tree = ttk.Treeview(frame_content, columns=columns, show="headings", height=15)
tree.pack(fill="both", expand=True, padx=10, pady=6)
for col, text in zip(columns, ["Waktu", "Jenis Aktivitas", "Nama File/Resep", "Keterangan"]):
tree.heading(col, text=text)
tree.column(col, anchor="w", width=150)
# --- Tombol Aksi (baris 1) ---
action_frame = tk.Frame(frame_content)
action_frame.pack(fill="x", pady=8)
def load_data():
tree.delete(*tree.get_children())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id DESC")
for row in cur.fetchall():
tree.insert("", "end", values=row)
conn.close()
def export_riwayat():
# Export ke file teks sederhana
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id")
rows = cur.fetchall()
conn.close()
if rows:
export_path = os.path.join(os.path.dirname(DB_PATH), "riwayat_export.txt")
with open(export_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(f"{r[0]} | {r[1]} | {r[2]} | {r[3]}\n")
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
ttk.Button(action_frame, text="Refresh", command=load_data).pack(side="left", padx=6)
ttk.Button(action_frame, text="Export Riwayat", command=export_riwayat).pack(side="left", padx=6)
ttk.Button(action_frame, text="Hapus Riwayat", command=hapus_riwayat).pack(side="left", padx=6)
# --- Tombol kembali di baris baru ---
back_frame = tk.Frame(frame_content)
back_frame.pack(fill="x", pady=8)
ttk.Button(back_frame, text="⬅ Kembali", command=go_back).pack(pady=5)
# --- Load awal ---
load_data()
return frame_content
2. Hook Global (Otomatis Logging Semua Menu/Submenu)
Buat file baru activity_logger.py:
# activity_logger.py
from riwayat_utils import catat_aktivitas
def with_logging(menu_func):
"""
Decorator untuk membungkus fungsi menu/submenu agar otomatis dicatat.
"""
def wrapper(*args, **kwargs):
catat_aktivitas(
jenis="Menu/SubMenu",
file=menu_func.__name__,
ket="Dibuka oleh user"
)
return menu_func(*args, **kwargs)
return wrapper
3. Pemakaian di Menu/Submenu Lain
Daripada panggil catat_aktivitas manual, cukup pakai decorator with_logging:
from activity_logger import with_logging
@with_logging
def sub_menu_tema_tampilan(root_frame, go_back):
frame = tk.Frame(root_frame)
tk.Label(frame, text="Tema Tampilan").pack()
return frame
Atau kalau fungsi sudah ada dan kamu tidak bisa pakai decorator, bisa bungkus manual saat dipanggil:
frame = with_logging(sub_menu_riwayat_penggunaan)(root_frame, go_back)
🚀 Jadi alurnya:
- Semua fungsi
menu_xxxatausub_menu_xxxotomatis tercatat ke SQLite begitu dipanggil. - Riwayat bisa langsung dilihat di sub_menu_riwayat_penggunaan karena sudah sinkron database.
Mau saya bikinkan juga contoh integrasi decorator with_logging ini langsung ke menu_pengaturan.py supaya kamu lihat implementasi nyatanya?.
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_riwayat_aktivitas.py", line 35, in
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/storage/emulated/0/AppJadi/SmartNutriChefAI/menu_riwayat_aktivitas.py", line 29, in open_submenu
catat_aktivitas(jenis=jenis, nama_file=func.name, keterangan="Dibuka oleh user")
TypeError: catat_aktivitas() got an unexpected keyword argument 'nama_file'
Mau kamu bikinkan juga contoh integrasi decorator with_logging ini langsung ke menu_pengaturan.py supaya saya lihat implementasi nyatanya.
Sip 👍, error yang muncul itu karena catat_aktivitas versi riwayat_utils.py pakainya argumen (jenis, file="", ket=""), tapi di kode lama masih pakai nama_file dan keterangan.
Kita sudah aman kalau semua pemanggilan pakai format baru (file, ket).
Sekarang saya bikinkan contoh nyata integrasi decorator with_logging ke menu_pengaturan.py.
🔹 activity_logger.py
# activity_logger.py
from riwayat_utils import catat_aktivitas
def with_logging(menu_func):
"""
Decorator untuk membungkus fungsi menu/submenu
agar otomatis tercatat di riwayat saat dibuka.
"""
def wrapper(*args, **kwargs):
catat_aktivitas(
jenis="Menu/SubMenu",
file=menu_func.__name__,
ket="Dibuka oleh user"
)
return menu_func(*args, **kwargs)
return wrapper
🔹 menu_pengaturan.py (contoh integrasi)
# menu_pengaturan.py
import tkinter as tk
from activity_logger import with_logging
from sub_menu_tema_tampilan import sub_menu_tema_tampilan
from sub_menu_bahasa_aplikasi import sub_menu_bahasa_aplikasi
from sub_menu_riwayat_penggunaan import sub_menu_riwayat_penggunaan
def show_menu_pengaturan(root_frame):
"""Tampilan utama menu Pengaturan"""
for w in root_frame.winfo_children():
w.destroy()
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
tk.Label(
frame_content,
text="⚙️ Menu Pengaturan",
font=("Arial", 16, "bold")
).pack(pady=15)
# Tombol Sub Menu Tema Tampilan
btn_tema = tk.Button(
frame_content,
text="🎨 Tema Tampilan",
width=25,
command=lambda: open_submenu(root_frame, sub_menu_tema_tampilan)
)
btn_tema.pack(pady=6)
# Tombol Sub Menu Bahasa Aplikasi
btn_bahasa = tk.Button(
frame_content,
text="🌐 Bahasa Aplikasi",
width=25,
command=lambda: open_submenu(root_frame, sub_menu_bahasa_aplikasi)
)
btn_bahasa.pack(pady=6)
# Tombol Sub Menu Riwayat Penggunaan
btn_riwayat = tk.Button(
frame_content,
text="📜 Riwayat Penggunaan",
width=25,
command=lambda: open_submenu(root_frame, sub_menu_riwayat_penggunaan)
)
btn_riwayat.pack(pady=6)
return frame_content
def open_submenu(root_frame, submenu_func):
"""Buka submenu dengan hook logging otomatis"""
for w in root_frame.winfo_children():
w.destroy()
# Bungkus fungsi submenu dengan logging
submenu_with_log = with_logging(submenu_func)
# Panggil submenu
submenu_with_log(root_frame, go_back=lambda: show_menu_pengaturan(root_frame))
🚀 Cara kerja:
-
Saat tombol submenu ditekan,
open_submenuakan memanggil versi dibungkuswith_loggingdari submenu. -
catat_aktivitasotomatis tercatat di database, dengan:jenis = "Menu/SubMenu"file = nama fungsi submenu(misal:sub_menu_tema_tampilan)ket = "Dibuka oleh user"
-
Tidak perlu lagi panggil
catat_aktivitasmanual di tiapsub_menu_xxx.
Mau saya bikinkan juga contoh output riwayat database (tabel isi riwayat) setelah user buka Tema Tampilan, Bahasa, lalu Riwayat → biar kamu lihat hasil log otomatisnya?
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_riwayat_aktivitas.py", line 35, in
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/storage/emulated/0/AppJadi/SmartNutriChefAI/menu_riwayat_aktivitas.py", line 29, in open_submenu
catat_aktivitas(jenis=jenis, nama_file=func.name, keterangan="Dibuka oleh user")
TypeError: catat_aktivitas() got an unexpected keyword argument 'nama_file'
Berikut ini file menu_riwayat_aktivitas.py:
Menu Riwayat Aktivitas
import tkinter as tk
from tkinter import ttk
from RiwayatAktivitas.sub_menu_riwayat_penggunaan import sub_menu_riwayat_penggunaan, catat_aktivitas
def show_menu_riwayat_aktivitas(root_frame):
from menu_utama import tampilkan_halaman_utama # import lokal agar aman
# Bersihkan frame utama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
label_title = tk.Label(
root_frame,
text="Riwayat Aktivitas",
font=("Arial", 16, "bold"),
bg="light grey"
)
label_title.pack(pady=10)
# Fungsi wrapper agar frame dibersihkan sebelum memanggil sub menu
def open_submenu(func, jenis="Submenu"):
for widget in root_frame.winfo_children():
widget.destroy()
# Catat ke riwayat
catat_aktivitas(jenis=jenis, nama_file=func.__name__, keterangan="Dibuka oleh user")
func(root_frame, lambda: show_menu_riwayat_aktivitas(root_frame))
# kirim go_back → kembali ke menu riwayat
# Submenu tombol
submenus = [
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
]
for text, command in submenus:
btn = tk.Button(
root_frame,
text=text,
font=("Arial", 10),
width=40,
height=2,
bg="light blue",
fg="dark green",
command=command
)
btn.pack(pady=5)
# Tombol kembali ke menu utama
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)
Berikut ini file sub_menu_riwayat_penggunaan.py:
sub_menu_riwayat_penggunaan.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
from datetime import datetime
from RiwayatAktivitas.riwayat_utils import catat_aktivitas, init_db # <-- Tambah ini
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def sub_menu_riwayat_penggunaan(root_frame, go_back):
"""Tampilan sub menu Lihat Riwayat Penggunaan"""
init_db()
# --- Catat otomatis setiap kali submenu ini dibuka ---
catat_aktivitas(
jenis="SubMenu",
file="sub_menu_riwayat_penggunaan",
ket="Dibuka oleh user"
)
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
# --- Judul ---
tk.Label(frame_content, text="Riwayat Penggunaan",
font=("Arial", 14, "bold")).pack(pady=10)
# --- Tabel Treeview ---
columns = ("waktu", "jenis", "file", "ket")
tree = ttk.Treeview(frame_content, columns=columns, show="headings", height=15)
tree.pack(fill="both", expand=True, padx=10, pady=6)
for col, text in zip(columns, ["Waktu", "Jenis Aktivitas", "Nama File/Resep", "Keterangan"]):
tree.heading(col, text=text)
tree.column(col, anchor="w", width=150)
# --- Tombol Aksi (baris 1) ---
action_frame = tk.Frame(frame_content)
action_frame.pack(fill="x", pady=8)
def load_data():
tree.delete(*tree.get_children())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id DESC")
for row in cur.fetchall():
tree.insert("", "end", values=row)
conn.close()
def export_riwayat():
# Export ke file teks sederhana
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id")
rows = cur.fetchall()
conn.close()
if rows:
export_path = os.path.join(os.path.dirname(DB_PATH), "riwayat_export.txt")
with open(export_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(f"{r[0]} | {r[1]} | {r[2]} | {r[3]}\n")
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
ttk.Button(action_frame, text="Refresh", command=load_data).pack(side="left", padx=6)
ttk.Button(action_frame, text="Export Riwayat", command=export_riwayat).pack(side="left", padx=6)
ttk.Button(action_frame, text="Hapus Riwayat", command=hapus_riwayat).pack(side="left", padx=6)
# --- Tombol kembali di baris baru ---
back_frame = tk.Frame(frame_content)
back_frame.pack(fill="x", pady=8)
ttk.Button(back_frame, text="⬅ Kembali", command=go_back).pack(pady=5)
# --- Load awal ---
load_data()
return frame_content
Berikut ini file riwayat_utils.py:
riwayat_utils.py
import sqlite3, os
from datetime import datetime
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
"""Inisialisasi database & tabel jika belum ada"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
ket TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, file="", ket=""):
"""Catat aktivitas ke database SQLite"""
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO riwayat (waktu, jenis, file, ket)
VALUES (?, ?, ?, ?)
""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, file, ket))
conn.commit()
conn.close()
Mau kamu bikinkan juga contoh output riwayat database (tabel isi riwayat) setelah user buka Tema Tampilan, Bahasa, lalu Riwayat → biar kamu lihat hasil log otomatisnya.
Oke 👍, saya sudah paham sumber error-nya.
Masalahnya ada di file menu_riwayat_aktivitas.py → masih pakai argumen lama:
catat_aktivitas(jenis=jenis, nama_file=func.__name__, keterangan="Dibuka oleh user")
Padahal di riwayat_utils.py sekarang definisinya:
def catat_aktivitas(jenis, file="", ket=""):
Jadi solusinya: ganti parameter agar sesuai.
✅ Revisi menu_riwayat_aktivitas.py
# Menu Riwayat Aktivitas
import tkinter as tk
from tkinter import ttk
from RiwayatAktivitas.sub_menu_riwayat_penggunaan import sub_menu_riwayat_penggunaan
from RiwayatAktivitas.riwayat_utils import catat_aktivitas
def show_menu_riwayat_aktivitas(root_frame):
from menu_utama import tampilkan_halaman_utama # import lokal agar aman
# Bersihkan frame utama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
label_title = tk.Label(
root_frame,
text="Riwayat Aktivitas",
font=("Arial", 16, "bold"),
bg="light grey"
)
label_title.pack(pady=10)
# Fungsi wrapper agar frame dibersihkan sebelum memanggil sub menu
def open_submenu(func, jenis="Submenu"):
for widget in root_frame.winfo_children():
widget.destroy()
# Catat ke riwayat (pakai argumen baru!)
catat_aktivitas(
jenis=jenis,
file=func.__name__,
ket="Dibuka oleh user"
)
func(root_frame, lambda: show_menu_riwayat_aktivitas(root_frame))
# kirim go_back → kembali ke menu riwayat
# Submenu tombol
submenus = [
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
]
for text, command in submenus:
btn = tk.Button(
root_frame,
text=text,
font=("Arial", 10),
width=40,
height=2,
bg="light blue",
fg="dark green",
command=command
)
btn.pack(pady=5)
# Tombol kembali ke menu utama
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)
✅ Contoh isi tabel riwayat setelah user buka:
- Tema Tampilan
- Bahasa Aplikasi
- Riwayat Penggunaan
Maka isi database (SELECT * FROM riwayat ORDER BY id) kira-kira:
| id | waktu | jenis | file | ket |
|---|---|---|---|---|
| 1 | 2025-09-22 10:01:05 | SubMenu | sub_menu_tema_tampilan | Dibuka oleh user |
| 2 | 2025-09-22 10:02:14 | SubMenu | sub_menu_bahasa_aplikasi | Dibuka oleh user |
| 3 | 2025-09-22 10:03:22 | Lihat Riwayat | sub_menu_riwayat_penggunaan | Dibuka oleh user |
👉 Jadi setiap kali submenu/menu dibuka, otomatis tercatat di tabel riwayat.
Dengan begitu, riwayat bisa langsung ditampilkan di TreeView tanpa perlu manual catat di setiap file.
Mau saya bikinkan query helper (misalnya get_all_riwayat(limit=20)) di riwayat_utils.py supaya bisa dipanggil langsung dari mana saja untuk debugging/menampilkan log terbaru?.
Berikut ini hasil akhir pembuatan kode untuk fitur riwayat aktivitas:
- Kode isi file menu_riwayat_aktivitas.py:
# Menu Riwayat Aktivitas
import tkinter as tk
from tkinter import ttk
from RiwayatAktivitas.sub_menu_riwayat_penggunaan import sub_menu_riwayat_penggunaan
from RiwayatAktivitas.riwayat_utils import catat_aktivitas
def show_menu_riwayat_aktivitas(root_frame):
from menu_utama import tampilkan_halaman_utama # import lokal agar aman
# Bersihkan frame utama
for widget in root_frame.winfo_children():
widget.destroy()
# Judul
label_title = tk.Label(
root_frame,
text="Riwayat Aktivitas",
font=("Arial", 16, "bold"),
bg="light grey"
)
label_title.pack(pady=10)
# Fungsi wrapper agar frame dibersihkan sebelum memanggil sub menu
def open_submenu(func, jenis="Submenu"):
for widget in root_frame.winfo_children():
widget.destroy()
# Catat ke riwayat (pakai argumen baru!)
catat_aktivitas(
jenis=jenis,
file=func.__name__,
ket="Dibuka oleh user"
)
func(root_frame, lambda: show_menu_riwayat_aktivitas(root_frame))
# kirim go_back → kembali ke menu riwayat
# Submenu tombol
submenus = [
("Lihat Riwayat Penggunaan", lambda: open_submenu(sub_menu_riwayat_penggunaan, "Lihat Riwayat")),
]
for text, command in submenus:
btn = tk.Button(
root_frame,
text=text,
font=("Arial", 10),
width=40,
height=2,
bg="light blue",
fg="dark green",
command=command
)
btn.pack(pady=5)
# Tombol kembali ke menu utama
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)
- Kode isi file sub_menu_riwayat_penggunaan.py:
# sub_menu_riwayat_penggunaan.py
import tkinter as tk
from tkinter import ttk, messagebox
import sqlite3, os
from datetime import datetime
from RiwayatAktivitas.riwayat_utils import catat_aktivitas, init_db # <-- Tambah ini
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def sub_menu_riwayat_penggunaan(root_frame, go_back):
"""Tampilan sub menu Lihat Riwayat Penggunaan"""
init_db()
# --- Catat otomatis setiap kali submenu ini dibuka ---
catat_aktivitas(
jenis="SubMenu",
file="sub_menu_riwayat_penggunaan",
ket="Dibuka oleh user"
)
frame_content = tk.Frame(root_frame)
frame_content.pack(fill="both", expand=True)
# --- Judul ---
tk.Label(frame_content, text="Riwayat Penggunaan",
font=("Arial", 14, "bold")).pack(pady=10)
# --- Tabel Treeview ---
columns = ("waktu", "jenis", "file", "ket")
tree = ttk.Treeview(frame_content, columns=columns, show="headings", height=15)
tree.pack(fill="both", expand=True, padx=10, pady=6)
for col, text in zip(columns, ["Waktu", "Jenis Aktivitas", "Nama File/Resep", "Keterangan"]):
tree.heading(col, text=text)
tree.column(col, anchor="w", width=150)
# --- Tombol Aksi (baris 1) ---
action_frame = tk.Frame(frame_content)
action_frame.pack(fill="x", pady=8)
def load_data():
tree.delete(*tree.get_children())
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id DESC")
for row in cur.fetchall():
tree.insert("", "end", values=row)
conn.close()
def export_riwayat():
# Export ke file teks sederhana
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("SELECT waktu, jenis, file, ket FROM riwayat ORDER BY id")
rows = cur.fetchall()
conn.close()
if rows:
export_path = os.path.join(os.path.dirname(DB_PATH), "riwayat_export.txt")
with open(export_path, "w", encoding="utf-8") as f:
for r in rows:
f.write(f"{r[0]} | {r[1]} | {r[2]} | {r[3]}\n")
messagebox.showinfo("Export", f"Riwayat berhasil diexport ke {export_path}")
else:
messagebox.showwarning("Export", "Belum ada riwayat untuk diexport.")
def hapus_riwayat():
if messagebox.askyesno("Konfirmasi", "Hapus semua riwayat?"):
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("DELETE FROM riwayat")
conn.commit()
conn.close()
load_data()
ttk.Button(action_frame, text="Refresh", command=load_data).pack(side="left", padx=6)
ttk.Button(action_frame, text="Export Riwayat", command=export_riwayat).pack(side="left", padx=6)
ttk.Button(action_frame, text="Hapus Riwayat", command=hapus_riwayat).pack(side="left", padx=6)
# --- Tombol kembali di baris baru ---
back_frame = tk.Frame(frame_content)
back_frame.pack(fill="x", pady=8)
ttk.Button(back_frame, text="⬅ Kembali", command=go_back).pack(pady=5)
# --- Load awal ---
load_data()
return frame_content
- Kode isi file riwayat_utils.py:
# riwayat_utils.py
import sqlite3, os
from datetime import datetime
DB_PATH = "/storage/emulated/0/AppJadi/SmartNutriChefAI/Database/RiwayatAktivitas/riwayat.db"
def init_db():
"""Inisialisasi database & tabel jika belum ada"""
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS riwayat (
id INTEGER PRIMARY KEY AUTOINCREMENT,
waktu TEXT,
jenis TEXT,
file TEXT,
ket TEXT
)
""")
conn.commit()
conn.close()
def catat_aktivitas(jenis, file="", ket=""):
"""Catat aktivitas ke database SQLite"""
init_db()
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
cur.execute("""
INSERT INTO riwayat (waktu, jenis, file, ket)
VALUES (?, ?, ?, ?)
""", (datetime.now().strftime("%Y-%m-%d %H:%M:%S"), jenis, file, ket))
conn.commit()
conn.close()
- Kode isi file activity_logger.py:
# activity_logger.py
from RiwayatAktivitas.riwayat_utils import catat_aktivitas
def with_logging(menu_func):
"""
Decorator untuk membungkus fungsi menu/submenu
agar otomatis tercatat di riwayat saat dibuka.
"""
def wrapper(*args, **kwargs):
catat_aktivitas(
jenis="Menu/SubMenu",
file=menu_func.__name__,
ket="Dibuka oleh user"
)
return menu_func(*args, **kwargs)
return wrapper
Demikian, semoga bermanfaat.
Comments
Post a Comment