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

  1. Tombol “Kembali” di sub_menu_riwayat_penggunaan harus benar-benar kembali ke menu riwayat aktivitas.
    → Saat ini sudah oke, karena kamu passing go_back dari menu_riwayat_aktivitas.py.

  2. 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 riwayat
  • riwayat_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:

  1. Di riwayat_utils.py, tabel sebaiknya riwayat saja (bukan riwayat_aktivitas), supaya sinkron dengan sub_menu.
  2. Semua fungsi init_db() dan catat_aktivitas() dipanggil dari riwayat_utils, bukan bikin ulang di sub_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_aktivitas cuma ada di riwayat_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:

  1. Integrasi langsung ke sub_menu_riwayat_penggunaan.py (otomatis catat aktivitas begitu submenu dibuka).
  2. Hook global yang bisa dipakai oleh semua menu/submenu, jadi tidak perlu panggil catat_aktivitas manual 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_xxx atau sub_menu_xxx otomatis 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_submenu akan memanggil versi dibungkus with_logging dari submenu.

  • catat_aktivitas otomatis 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_aktivitas manual di tiap sub_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:

  1. Tema Tampilan
  2. Bahasa Aplikasi
  3. 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

Popular posts from this blog

Isi Menu "Setting"