saindras

pbo-pertemuan-11-gui_app_2.py

Nov 11th, 2025 (edited)
16
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 18.22 KB | None | 0 0
  1. import sys
  2. import os
  3. from PySide6.QtWidgets import (
  4.     QApplication, QMainWindow, QMessageBox, QLineEdit,
  5.     QPushButton, QTreeWidget, QTreeWidgetItem, QComboBox, QLabel
  6. )
  7. from PySide6.QtUiTools import QUiLoader
  8. from PySide6.QtCore import Slot, QObject  # Import QObject jika diperlukan
  9.  
  10. # Import backend Anda, SAMA SEKALI TIDAK BERUBAH
  11. from perpustakaan_backend import Perpustakaan, Pustakawan, StatusItem, StatusTransaksi
  12.  
  13. class MainWindow(QMainWindow):
  14.     """
  15.    Kelas utama yang memuat .ui file dan menghubungkan logika (slots).
  16.    Ini menggantikan kelas AppPerpustakaan dari app_gui.py.
  17.    """
  18.     def __init__(self):
  19.         super().__init__()
  20.  
  21.         # 1. Muat file .ui
  22.         loader = QUiLoader()
  23.         # Tentukan path. Asumsi .ui file ada di folder yang sama
  24.         path = os.path.join(os.path.dirname(__file__), "perpustakaan_ui.ui")
  25.         self.ui = loader.load(path, self) # Simpan sebagai self.ui
  26.        
  27.         # --- TAMBAHKAN BARIS INI UNTUK MENAMPILKAN JENDELA ---
  28.         self.ui.show()
  29.         # --------------------------------------------------
  30.  
  31.         # 2. Inisialisasi Backend (sama seperti app_gui.py)
  32.         try:
  33.             # Sesuaikan path jika perlu
  34.             nama_file_db = "perpus_data.pkl"
  35.             self.perpustakaan_utama = Perpustakaan(nama_file_db)
  36.         except Exception as e:
  37.             QMessageBox.critical(self, "Startup Error", f"Gagal memuat data: {e}")
  38.             self.perpustakaan_utama = Perpustakaan(nama_file_db)
  39.            
  40.         self.pustakawan_andi = Pustakawan("P-001", "Andi")
  41.        
  42.         # Inisialisasi data awal (sama)
  43.         if not self.perpustakaan_utama.daftar_item:
  44.             print("INFO: Menambahkan data item awal untuk demo...")
  45.             self.pustakawan_andi.tambah_item_ke_koleksi(self.perpustakaan_utama, "buku", "BK-001", "OOP Python", pengarang="Dr. Inovatif")
  46.             self.pustakawan_andi.tambah_item_ke_koleksi(self.perpustakaan_utama, "majalah", "MGZ-001", "Quanta", edisi="Okt 2025")
  47.             self.pustakawan_andi.tambah_item_ke_koleksi(self.perpustakaan_utama, "buku", "BK-002", "Sains Data", pengarang="Prof. Analitika")
  48.  
  49.         # 3. Temukan Widget (menggunakan findChild)
  50.         # Tab 1 - Form Member
  51.         self.entry_nama = self.ui.findChild(QLineEdit, "entry_nama")
  52.         self.entry_alamat = self.ui.findChild(QLineEdit, "entry_alamat")
  53.         self.tombol_submit_member = self.ui.findChild(QPushButton, "tombol_submit_member")
  54.        
  55.         # Tab 1 - Form Item
  56.         self.combo_tipe_item = self.ui.findChild(QComboBox, "combo_tipe_item")
  57.         self.entry_item_id = self.ui.findChild(QLineEdit, "entry_item_id")
  58.         self.entry_item_judul = self.ui.findChild(QLineEdit, "entry_item_judul")
  59.         self.label_khusus = self.ui.findChild(QLabel, "label_khusus")
  60.         self.entry_khusus = self.ui.findChild(QLineEdit, "entry_khusus")
  61.         self.tombol_submit_item = self.ui.findChild(QPushButton, "tombol_submit_item")
  62.  
  63.         # Tab 1 - Tree Koleksi
  64.         self.tombol_refresh_daftar_koleksi_item = self.ui.findChild(QPushButton, "tombol_refresh_daftar_koleksi_item")
  65.         self.tree_buku = self.ui.findChild(QTreeWidget, "tree_buku")
  66.        
  67.         # Tab 1 - Tree Member
  68.         self.tombol_refresh_member = self.ui.findChild(QPushButton, "tombol_refresh_member")
  69.         self.tombol_riwayat_member = self.ui.findChild(QPushButton, "tombol_riwayat_member")
  70.         self.tree_member = self.ui.findChild(QTreeWidget, "tree_member")
  71.        
  72.         # Tab 1 - Tree Riwayat
  73.         self.label_riwayat_member = self.ui.findChild(QLabel, "label_riwayat_member")
  74.         self.tree_riwayat = self.ui.findChild(QTreeWidget, "tree_riwayat")
  75.        
  76.         # Tab 2 - Peminjaman (KOREKSI: QLineEdit -> QComboBox)
  77.         self.combo_pinjam_member_id = self.ui.findChild(QComboBox, "entry_pinjam_member_id")
  78.         self.combo_pinjam_item_id = self.ui.findChild(QComboBox, "entry_pinjam_item_id")
  79.         self.tombol_pinjam = self.ui.findChild(QPushButton, "tombol_pinjam")
  80.        
  81.         # Tab 2 - Pengembalian (KOREKSI: QLineEdit -> QComboBox)
  82.         self.combo_kembali_trx_id = self.ui.findChild(QComboBox, "entry_kembali_trx_id")
  83.         self.tombol_kembali = self.ui.findChild(QPushButton, "tombol_kembali")
  84.        
  85.         # Tombol Bawah
  86.         self.tombol_simpan = self.ui.findChild(QPushButton, "tombol_simpan")
  87.  
  88.         # 4. Hubungkan Signals ke Slots (Callbacks)
  89.         # Format: widget.signal.connect(self.fungsi_callback)
  90.         self.tombol_submit_member.clicked.connect(self.daftar_member_baru)
  91.         self.tombol_submit_item.clicked.connect(self.tambah_item_baru)
  92.         self.combo_tipe_item.currentTextChanged.connect(self.on_tipe_item_changed) # Menggantikan .bind()
  93.        
  94.         self.tombol_refresh_daftar_koleksi_item.clicked.connect(self.refresh_daftar_buku)
  95.         self.tombol_refresh_member.clicked.connect(self.refresh_daftar_member)
  96.         self.tombol_riwayat_member.clicked.connect(self.tampilkan_riwayat_terpilih)
  97.        
  98.         self.tombol_pinjam.clicked.connect(self.lakukan_peminjaman)
  99.         self.tombol_kembali.clicked.connect(self.lakukan_pengembalian)
  100.        
  101.         self.tombol_simpan.clicked.connect(self.simpan_dan_keluar)
  102.  
  103.         # 5. Refresh data awal
  104.         self.refresh_daftar_buku()
  105.         self.refresh_daftar_member()
  106.        
  107.         # BARU: Panggil fungsi untuk mengisi combobox
  108.         self.refresh_combobox_member()
  109.         self.refresh_combobox_item_tersedia()
  110.         self.refresh_combobox_transaksi_aktif()
  111.        
  112.         # Set kolom TreeView agar stretch (opsional tapi bagus)
  113.         self.tree_buku.header().setStretchLastSection(True)
  114.         self.tree_member.header().setStretchLastSection(True)
  115.         self.tree_riwayat.header().setStretchLastSection(True)
  116.  
  117.     # ==============================================================================
  118.     # SLOTS (CALLBACK FUNCTIONS) - Diterjemahkan dari Tkinter ke PySide6
  119.     # ==============================================================================
  120.  
  121.     @Slot()
  122.     def daftar_member_baru(self):
  123.         print("DEBUG: Tombol Daftar Member Ditekan")
  124.        
  125.         # 1. Ambil data -> .get() diganti .text()
  126.         nama = self.entry_nama.text()
  127.         alamat = self.entry_alamat.text()
  128.  
  129.         # 2. Validasi input
  130.         if not nama or not alamat:
  131.             # messagebox.showwarning -> QMessageBox.warning
  132.             QMessageBox.warning(self, "Input Tidak Lengkap", "Nama dan Alamat tidak boleh kosong!")
  133.             return
  134.  
  135.         # 3. Panggil logika backend (TIDAK BERUBAH)
  136.         try:
  137.             member_baru = self.pustakawan_andi.lakukan_pendaftaran(self.perpustakaan_utama, nama, alamat)
  138.            
  139.             # 4. Beri umpan balik
  140.             QMessageBox.information(self, "Pendaftaran Berhasil",
  141.                                   f"Member baru berhasil ditambahkan!\n"
  142.                                   f"Nama: {member_baru.nama}\n"
  143.                                   f"ID: {member_baru.member_id}")
  144.            
  145.             # 5. Kosongkan form -> .delete(0, tk.END) diganti .clear()
  146.             self.entry_nama.clear()
  147.             self.entry_alamat.clear()
  148.            
  149.             # 6. Refresh daftar member
  150.             self.refresh_daftar_member()
  151.             # BARU: Refresh juga combobox member
  152.             self.refresh_combobox_member()
  153.  
  154.         except Exception as e:
  155.             QMessageBox.critical(self, "Pendaftaran Gagal", f"Terjadi error: {e}")
  156.    
  157.     @Slot()
  158.     def refresh_daftar_buku(self):
  159.         # Hapus data lama -> tree.delete(tree.get_children()) diganti .clear()
  160.         self.tree_buku.clear()
  161.        
  162.         # Ambil data baru dari backend
  163.         for item in self.perpustakaan_utama.daftar_item:
  164.             info = item.get_info()
  165.             # Buat item tree baru -> .insert() diganti QTreeWidgetItem
  166.             tree_item = QTreeWidgetItem([info["ID"], info["Judul"], info["Status"]])
  167.             self.tree_buku.addTopLevelItem(tree_item)
  168.  
  169.     @Slot()
  170.     def refresh_daftar_member(self):
  171.         self.tree_member.clear()
  172.        
  173.         for member in self.perpustakaan_utama.daftar_member:
  174.             tree_item = QTreeWidgetItem([member.member_id, member.nama, member._alamat])
  175.             self.tree_member.addTopLevelItem(tree_item)
  176.  
  177.     @Slot()
  178.     def lakukan_peminjaman(self):
  179.         # KOREKSI: Ambil data dari Combobox
  180.         member_id_full = self.combo_pinjam_member_id.currentText()
  181.         item_id_full = self.combo_pinjam_item_id.currentText()
  182.        
  183.         if not member_id_full or not item_id_full:
  184.             QMessageBox.warning(self, "Input Tidak Lengkap", "ID Member dan ID Item harus dipilih.")
  185.             return
  186.  
  187.         # KOREKSI: Ekstrak ID dari string (format: "ID - Nama")
  188.         try:
  189.             member_id = member_id_full.split(' - ')[0]
  190.             item_id = item_id_full.split(' - ')[0]
  191.         except Exception:
  192.             QMessageBox.critical(self, "Format Salah", "Format data di combobox salah.")
  193.             return
  194.  
  195.         try:
  196.             trx_baru = self.pustakawan_andi.lakukan_peminjaman(self.perpustakaan_utama, member_id, item_id)
  197.             QMessageBox.information(self, "Peminjaman Berhasil",
  198.                                   f"Transaksi {trx_baru.transaksi_id} sukses.\n"
  199.                                   f"Item '{trx_baru.item.judul}' dipinjam oleh {trx_baru.member.nama}.")
  200.             # KOREKSI: .clear() diganti .setCurrentIndex(-1) untuk reset pilihan
  201.             self.combo_pinjam_member_id.setCurrentIndex(-1)
  202.             self.combo_pinjam_item_id.setCurrentIndex(-1)
  203.             self.refresh_daftar_buku()
  204.             # BARU: Refresh combobox item (karena ada yg dipinjam) dan transaksi
  205.             self.refresh_combobox_item_tersedia()
  206.             self.refresh_combobox_transaksi_aktif()
  207.         except Exception as e:
  208.             QMessageBox.critical(self, "Peminjaman Gagal", f"Terjadi error: {e}")
  209.  
  210.     @Slot()
  211.     def lakukan_pengembalian(self):
  212.         # KOREKSI: Ambil data dari Combobox
  213.         trx_id_full = self.combo_kembali_trx_id.currentText()
  214.        
  215.         if not trx_id_full:
  216.             QMessageBox.warning(self, "Input Tidak Lengkap", "ID Transaksi harus dipilih.")
  217.             return
  218.  
  219.         # KOREKSI: Ekstrak ID dari string
  220.         try:
  221.             trx_id = trx_id_full.split(' - ')[0]
  222.         except Exception:
  223.             QMessageBox.critical(self, "Format Salah", "Format data di combobox salah.")
  224.             return
  225.            
  226.         try:
  227.             # Logika validasi (TIDAK BERUBAH)
  228.             transaksi = self.perpustakaan_utama.cari_transaksi(trx_id)
  229.             if not transaksi:
  230.                 raise Exception(f"Transaksi {trx_id} tidak ditemukan.")
  231.             if transaksi.status not in (StatusTransaksi.AKTIF, StatusTransaksi.TERLAMBAT):
  232.                     raise Exception(f"Transaksi {trx_id} sudah tidak aktif/selesai.")
  233.                    
  234.             denda = transaksi.hitung_denda()
  235.            
  236.             # Lakukan pengembalian (TIDAK BERUBAH)
  237.             self.pustakawan_andi.lakukan_pengembalian(self.perpustakaan_utama, trx_id)
  238.            
  239.             pesan_denda = f"Denda: Rp {denda:,.2f}" if denda > 0 else "Tidak ada denda."
  240.             QMessageBox.information(self, "Pengembalian Berhasil",
  241.                                   f"Transaksi {trx_id} sukses dikembalikan.\n{pesan_denda}")
  242.  
  243.             # KOREKSI: .clear() diganti .setCurrentIndex(-1) untuk reset pilihan
  244.             self.combo_kembali_trx_id.setCurrentIndex(-1)
  245.             self.refresh_daftar_buku()
  246.             # BARU: Refresh combobox item (karena ada yg kembali) dan transaksi
  247.             self.refresh_combobox_item_tersedia()
  248.             self.refresh_combobox_transaksi_aktif()
  249.             self.tampilkan_riwayat_terpilih(auto_refresh=True)
  250.            
  251.         except Exception as e:
  252.             QMessageBox.critical(self, "Pengembalian Gagal", f"Terjadi error: {e}")
  253.  
  254.     @Slot()
  255.     def tampilkan_riwayat_terpilih(self, auto_refresh=False):
  256.         # Mengambil item terpilih -> .focus() diganti .currentItem()
  257.         selected_item = self.tree_member.currentItem()
  258.        
  259.         if not selected_item:
  260.             if not auto_refresh:
  261.                 QMessageBox.warning(self, "Pilihan Kosong", "Pilih salah satu member dari tabel untuk melihat riwayat.")
  262.             return
  263.  
  264.         # Mengambil data dari item terpilih -> .item(id)['values'] diganti .text(col_index)
  265.         member_id = selected_item.text(0)
  266.         member_nama = selected_item.text(1)
  267.        
  268.         member = self.perpustakaan_utama.cari_member(member_id)
  269.         if not member:
  270.             QMessageBox.critical(self, "Error", f"Data member {member_id} tidak ditemukan di backend.")
  271.             return
  272.  
  273.         # Update label -> .config(text=...) diganti .setText(...)
  274.         self.label_riwayat_member.setText(f"Menampilkan riwayat untuk: {member_nama} (ID: {member_id})")
  275.        
  276.         self.tree_riwayat.clear()
  277.            
  278.         riwayat = member.get_riwayat_peminjaman(self.perpustakaan_utama)
  279.         if not riwayat:
  280.             item = QTreeWidgetItem(["", "Tidak ada riwayat peminjaman.", "", "", "", ""])
  281.             self.tree_riwayat.addTopLevelItem(item)
  282.             return
  283.            
  284.         for trx in riwayat:
  285.             item = trx.item
  286.             denda = trx.hitung_denda() if trx.status == StatusTransaksi.TERLAMBAT else trx._denda
  287.            
  288.             # Buat list string untuk data
  289.             data_row = [
  290.                 trx.transaksi_id,
  291.                 item.judul,
  292.                 trx._tgl_pinjam.strftime('%Y-%m-%d'),
  293.                 trx._tgl_kembali.strftime('%Y-%m-%d'),
  294.                 trx.status.value,
  295.                 f"Rp{denda:,.2f}"
  296.             ]
  297.             tree_item = QTreeWidgetItem(data_row)
  298.             self.tree_riwayat.addTopLevelItem(tree_item)
  299.  
  300.     @Slot()
  301.     def on_tipe_item_changed(self, tipe_terpilih):
  302.         """Mengubah label form berdasarkan pilihan Combobox."""
  303.         if tipe_terpilih == "Buku":
  304.             self.label_khusus.setText("Pengarang:")
  305.         elif tipe_terpilih == "Majalah":
  306.             self.label_khusus.setText("Edisi:")
  307.         self.entry_khusus.clear()
  308.  
  309.     @Slot()
  310.     def tambah_item_baru(self):
  311.         print("DEBUG: Tombol Tambah Item Ditekan")
  312.        
  313.         tipe = self.combo_tipe_item.currentText() # .get() -> .currentText()
  314.         item_id = self.entry_item_id.text()
  315.         judul = self.entry_item_judul.text()
  316.         khusus = self.entry_khusus.text()
  317.  
  318.         if not all([tipe, item_id, judul, khusus]):
  319.             QMessageBox.warning(self, "Input Tidak Lengkap", "Semua field harus diisi!")
  320.             return
  321.  
  322.         kwargs = {}
  323.         if tipe == "Buku":
  324.             kwargs['pengarang'] = khusus
  325.         elif tipe == "Majalah":
  326.             kwargs['edisi'] = khusus
  327.  
  328.         try:
  329.             self.pustakawan_andi.tambah_item_ke_koleksi(
  330.                 self.perpustakaan_utama,
  331.                 tipe.lower(),
  332.                 item_id,
  333.                 judul,
  334.                 **kwargs
  335.             )
  336.            
  337.             QMessageBox.information(self, "Penambahan Berhasil", f"{tipe} '{judul}' berhasil ditambahkan ke koleksi.")
  338.            
  339.             self.entry_item_id.clear()
  340.             self.entry_item_judul.clear()
  341.             self.entry_khusus.clear()
  342.            
  343.             self.refresh_daftar_buku()
  344.             # BARU: Refresh combobox item
  345.             self.refresh_combobox_item_tersedia()
  346.  
  347.         except Exception as e:
  348.             # KOREKSI: Seharusnya .critical() bukan .clear()
  349.             QMessageBox.critical(self, "Penambahan Gagal", f"Terjadi error: {e}")
  350.  
  351.     # --- FUNGSI HELPER BARU UNTUK COMBOBOX ---
  352.  
  353.     @Slot()
  354.     def refresh_combobox_member(self):
  355.         """Mengisi combobox member dengan data (ID - Nama)."""
  356.         self.combo_pinjam_member_id.clear() # Hapus item lama
  357.         try:
  358.             member_list = [f"{m.member_id} - {m.nama}" for m in self.perpustakaan_utama.daftar_member]
  359.             self.combo_pinjam_member_id.addItems(member_list)
  360.             self.combo_pinjam_member_id.setCurrentIndex(-1) # Kosongkan pilihan
  361.         except Exception as e:
  362.             print(f"ERROR: Gagal refresh combobox member: {e}")
  363.  
  364.     @Slot()
  365.     def refresh_combobox_item_tersedia(self):
  366.         """Mengisi combobox item dengan item yang TERSEDIA (ID - Judul)."""
  367.         self.combo_pinjam_item_id.clear() # Hapus item lama
  368.         try:
  369.             item_list = [f"{i.item_id} - {i.judul}" for i in self.perpustakaan_utama.daftar_item if i.status == StatusItem.TERSEDIA]
  370.             self.combo_pinjam_item_id.addItems(item_list)
  371.             self.combo_pinjam_item_id.setCurrentIndex(-1) # Kosongkan pilihan
  372.         except Exception as e:
  373.             print(f"ERROR: Gagal refresh combobox item: {e}")
  374.            
  375.     @Slot()
  376.     def refresh_combobox_transaksi_aktif(self):
  377.         """Mengisi combobox transaksi dengan transaksi AKTIF (ID - Judul Item)."""
  378.         self.combo_kembali_trx_id.clear() # Hapus item lama
  379.         try:
  380.             trx_list = [
  381.                 f"{t.transaksi_id} - ({t.item.judul} oleh {t.member.nama})"
  382.                 for t in self.perpustakaan_utama.daftar_transaksi
  383.                 if t.status == StatusTransaksi.AKTIF or t.status == StatusTransaksi.TERLAMBAT
  384.             ]
  385.             self.combo_kembali_trx_id.addItems(trx_list)
  386.             self.combo_kembali_trx_id.setCurrentIndex(-1) # Kosongkan pilihan
  387.         except Exception as e:
  388.             print(f"ERROR: Gagal refresh combobox transaksi: {e}")
  389.            
  390.     @Slot()
  391.     def simpan_dan_keluar(self):
  392.         try:
  393.             self.perpustakaan_utama.simpan_data()
  394.             QMessageBox.information(self, "Sukses", "Data berhasil disimpan!")
  395.             self.ui.close() # .destroy() diganti .close()
  396.            
  397.             # --- PERBAIKAN: Tambahkan baris ini ---
  398.             # Ini akan memberi tahu app.exec() untuk berhenti,
  399.             # yang akan mengakhiri script.
  400.             QApplication.instance().quit()
  401.            
  402.         except Exception as e:
  403.             QMessageBox.critical(self, "Gagal Menyimpan", f"Terjadi error: {e}")
  404.  
  405. # --- Titik Mulai Eksekusi ---
  406. if __name__ == "__main__":
  407.     app = QApplication(sys.argv)
  408.     window = MainWindow()
  409.     # .ui file sekarang sudah di-show() di dalam __init__
  410.     sys.exit(app.exec())
Add Comment
Please, Sign In to add comment