diff --git a/lap/tes3.png b/lap/tes3.png new file mode 100644 index 0000000..988d652 Binary files /dev/null and b/lap/tes3.png differ diff --git a/lap/tes4.png b/lap/tes4.png new file mode 100644 index 0000000..2f13d20 Binary files /dev/null and b/lap/tes4.png differ diff --git a/lap/tes5.png b/lap/tes5.png new file mode 100644 index 0000000..f44c56b Binary files /dev/null and b/lap/tes5.png differ diff --git a/main.py b/main.py index 5e4f7a9..7f0ff71 100644 --- a/main.py +++ b/main.py @@ -3316,7 +3316,14 @@ class App: text="💾 Export Laporan", command=self.export_report_to_file, style="Accent.TButton" - ).grid(row=3, column=0, pady=10) + ).grid(row=3, column=0, pady=(10, 5)) + + ttk.Button( + filter_frame, + text="🖼️ Export Grafik (PNG)", + command=self.export_report_chart, + style="Accent.TButton" + ).grid(row=4, column=0, pady=5) # Summary frame summary_frame = ttk.LabelFrame(parent, text="📈 Ringkasan", padding=10) @@ -3851,6 +3858,135 @@ class App: # Fallback: Tampilkan grafik ASCII sederhana self.show_text_chart() + def export_report_chart(self): + """Export grafik laporan ke file gambar (PNG) - REVISI FREKUENSI TRANSAKSI""" + try: + import matplotlib.pyplot as plt + from matplotlib.ticker import MaxNLocator # Untuk sumbu Y bilangan bulat + from datetime import datetime + except ImportError: + messagebox.showerror("Error", "Modul 'matplotlib' belum terinstall.\nSilakan install dengan: pip install matplotlib") + return + + # 1. Cek Data + if not self.report_tree.get_children(): + messagebox.showwarning("Data Kosong", "Generate laporan terlebih dahulu sebelum export grafik.") + return + + # Ambil periode saat ini ('harian', 'mingguan', atau 'bulanan') + periode_pilihan = self.report_period_var.get() + + # 2. Kumpulkan Data dari Treeview + metode_counts = {} + trend_data = {} # Bisa per jam atau per hari tergantung periode + + for item_id in self.report_tree.get_children(): + values = self.report_tree.item(item_id)['values'] + # Format values: (tid, tanggal, meja, total_str, metode, status) + tanggal_str = values[1] # Format: DD/MM/YYYY HH:MM + metode = values[4] + + # Hitung Pie Chart (Metode) + metode_counts[metode] = metode_counts.get(metode, 0) + 1 + + # Hitung Bar Chart (Trend Transaksi) + try: + dt_obj = datetime.strptime(tanggal_str, "%d/%m/%Y %H:%M") + + if periode_pilihan == 'harian': + # Jika Harian: Group by JAM (misal "14:00") + key = dt_obj.strftime("%H:00") + else: + # Jika Mingguan/Bulanan: Group by TANGGAL (misal "2025-12-14") + key = dt_obj.strftime("%Y-%m-%d") + + # Kita hitung JUMLAH TRANSAKSI (+1), bukan total uang + trend_data[key] = trend_data.get(key, 0) + 1 + + except ValueError: + pass + + # 3. Buat Figure & Plotting + try: + # Setup layout: 1 Baris, 2 Kolom + fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7)) + + judul_periode = periode_pilihan.upper() + fig.suptitle(f'LAPORAN TRANSAKSI - {judul_periode}', fontsize=16, fontweight='bold') + + # --- PLOT 1: PIE CHART (Metode Pembayaran) --- + if metode_counts: + labels = list(metode_counts.keys()) + sizes = list(metode_counts.values()) + # Warna-warni pastel agar cantik + colors = ['#ff9999','#66b3ff','#99ff99','#ffcc99', '#c2c2f0'] + ax1.pie(sizes, labels=labels, autopct='%1.0f%%', colors=colors[:len(labels)], startangle=90, shadow=True) + ax1.set_title('Proporsi Metode Pembayaran') + else: + ax1.text(0.5, 0.5, "Tidak ada data", ha='center') + + # --- PLOT 2: BAR CHART (Trend Frekuensi Transaksi) --- + if trend_data: + keys = sorted(trend_data.keys()) + counts = [trend_data[k] for k in keys] + + # Label X-Axis (Formatting agar rapi) + if periode_pilihan == 'harian': + # Label jam tetap apa adanya + x_labels = keys + x_title = "Jam Transaksi" + chart_title = "Jumlah Transaksi per Jam (Busy Hours)" + else: + # Format tanggal jadi lebih pendek (DD/MM) + x_labels = [datetime.strptime(k, "%Y-%m-%d").strftime("%d/%m") for k in keys] + x_title = "Tanggal" + chart_title = "Total Transaksi per Hari" + + bars = ax2.bar(x_labels, counts, color='#4ECDC4', zorder=3, edgecolor='black', linewidth=0.7) + + ax2.set_title(chart_title) + ax2.set_xlabel(x_title) + ax2.set_ylabel('Jumlah Transaksi (Struk)') + + # Grid garis putus-putus di belakang + ax2.grid(axis='y', linestyle='--', alpha=0.5, zorder=0) + + # Pastikan Sumbu Y hanya menampilkan Angka Bulat (Integer) + # Karena tidak mungkin ada 1.5 transaksi + ax2.yaxis.set_major_locator(MaxNLocator(integer=True)) + + # Tambah label angka di atas setiap batang + for bar in bars: + height = bar.get_height() + ax2.text(bar.get_x() + bar.get_width()/2., height, + f'{int(height)}', + ha='center', va='bottom', fontsize=10, fontweight='bold') + + # Putar label x jika data banyak agar tidak tabrakan + if len(x_labels) > 5: + plt.setp(ax2.get_xticklabels(), rotation=45, ha="right") + + else: + ax2.text(0.5, 0.5, "Tidak ada data transaksi", ha='center') + + plt.tight_layout() + + # 4. Dialog Simpan File + filename = filedialog.asksaveasfilename( + defaultextension=".png", + filetypes=[("PNG Image", "*.png"), ("JPEG Image", "*.jpg")], + title="Simpan Grafik Statistik" + ) + + if filename: + plt.savefig(filename, dpi=150, bbox_inches='tight') + messagebox.showinfo("Sukses", f"Grafik berhasil disimpan:\n{filename}") + + plt.close(fig) + + except Exception as e: + messagebox.showerror("Export Gagal", f"Terjadi kesalahan: {e}") + def reload_payment_orders(self):