upgrade and improve the visuals for this script, do not omit anything and please add a tab where the user can create a template to use for the invoices generated by the program.
import sys
import json
import os
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import pandas as pd
from PyQt6 import QtWidgets, QtGui, QtCore
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QTabWidget, QHBoxLayout,
    QLabel, QLineEdit, QPushButton, QMessageBox, QComboBox,
    QTextEdit, QListWidget, QListWidgetItem, QFileDialog, QSpinBox
)

# Define file paths
CLIENTS_FILE = 'clients.json'
LINE_ITEMS_FILE = 'line_items.json'
INVOICES_DIR = 'invoices'
EMAIL_CONFIG_FILE = 'email_config.json'

# Ensure invoices directory exists
os.makedirs(INVOICES_DIR, exist_ok=True)

# Function to load data from JSON
def load_json(file_path):
    if os.path.exists(file_path):
        with open(file_path, 'r') as f:
            return json.load(f)
    return {}

# Function to save data to JSON
def save_json(data, file_path):
    with open(file_path, 'w') as f:
        json.dump(data, f, indent=4)

# Load initial data
clients = load_json(CLIENTS_FILE)
line_items_data = load_json(LINE_ITEMS_FILE)
email_config = load_json(EMAIL_CONFIG_FILE)

# GUI Application
class QuickBooksReplacement(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.clients = load_json(CLIENTS_FILE)
        self.line_items_data = load_json(LINE_ITEMS_FILE)
        self.email_config = load_json(EMAIL_CONFIG_FILE)
        self.invoice_counter = self.get_next_invoice_number()  # Initialize invoice counter
        self.init_ui()

    def get_next_invoice_number(self):
        """Gets the next available invoice number."""
        if os.path.exists("invoice_counter.txt"):
            with open("invoice_counter.txt", "r") as f:
                try:
                    counter = int(f.read())
                    return counter + 1
                except ValueError:
                    return 1
        else:
            return 1

    def update_invoice_counter(self):
        """Updates the invoice counter file."""
        with open("invoice_counter.txt", "w") as f:
            f.write(str(self.invoice_counter))

    def init_ui(self):
        self.setWindowTitle("QuickBooks Replacement Tool")
        self.setGeometry(100, 100, 800, 600)
        layout = QVBoxLayout()

        # Apply Dark Theme and White Text
        self.apply_dark_theme()

        # Tab Widget
        self.tabs = QTabWidget()
        layout.addWidget(self.tabs)

        # Tabs
        self.client_tab = ClientManagementTab(self)
        self.line_item_tab = LineItemManagementTab(self)
        self.invoice_tab = InvoiceCreationTab(self)
        self.manage_invoices_tab = ManageInvoicesTab(self)
        self.email_config_tab = EmailConfigTab(self)

        self.tabs.addTab(self.client_tab, "Clients")
        self.tabs.addTab(self.line_item_tab, "Line Items")
        self.tabs.addTab(self.invoice_tab, "Create Invoice")
        self.tabs.addTab(self.manage_invoices_tab, "Manage Invoices")
        self.tabs.addTab(self.email_config_tab, "Email Settings")

        # Log Message Area
        self.log_area = QTextEdit(self)
        self.log_area.setReadOnly(True)
        self.log_area.setStyleSheet("""
            background-color: #1E1E1E;
            color: #ffffff;
            font-family: Consolas;
            font-size: 12px;
        """)
        layout.addWidget(QLabel("Log:"))
        layout.addWidget(self.log_area)

        self.setLayout(layout)

    def apply_dark_theme(self):
        dark_stylesheet = """
            /* General Widget Styles */
            QWidget {
                background-color: #121212;
                color: #ffffff;
                font-family: "Segoe UI", sans-serif;
                font-size: 14px;
            }
            QLabel {
                color: #ffffff;
            }
            QLineEdit, QTextEdit, QComboBox, QListWidget, QSpinBox {
                background-color: #1E1E1E;
                border: 1px solid #555555;
                color: #ffffff;
            }
            QPushButton {
                background-color: #2D2D30;
                border: 1px solid #555555;
                padding: 5px;
                color: #ffffff;
            }
            QPushButton:hover {
                background-color: #3E3E42;
            }
            QPushButton:pressed {
                background-color: #5A5A5E;
            }
            QTabWidget::pane {
                border: 1px solid #555555;
            }
            QTabBar::tab {
                background: #2D2D30;
                border: 1px solid #555555;
                padding: 10px;
                margin: 2px;
                color: #ffffff;
            }
            QTabBar::tab:selected {
                background: #1E90FF;
                color: #ffffff;
            }
            QMenuBar {
                background-color: #121212;
                color: #ffffff;
            }
            QMenu {
                background-color: #1E1E1E;
                color: #ffffff;
            }
            QMenu::item:selected {
                background-color: #3E3E42;
            }
            QListWidget::item:selected {
                background-color: #1E90FF;
                color: #ffffff;
            }
            QHeaderView::section {
                background-color: #2D2D30;
                color: #ffffff;
                padding: 4px;
                border: 1px solid #555555;
            }
        """
        self.setStyleSheet(dark_stylesheet)

    def log_message(self, message):
        """Logs message to the text area."""
        self.log_area.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")

class ClientManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Form to add new client
        form_layout = QHBoxLayout()
        self.client_name_input = QLineEdit(self)
        self.client_name_input.setPlaceholderText("Client Name")
        self.client_email_input = QLineEdit(self)
        self.client_email_input.setPlaceholderText("Client Email")
        self.client_address_input = QLineEdit(self)
        self.client_address_input.setPlaceholderText("Client Address")
        self.add_client_button = QPushButton("Add Client")
        self.add_client_button.clicked.connect(self.add_client)

        form_layout.addWidget(QLabel("Name:"))
        form_layout.addWidget(self.client_name_input)
        form_layout.addWidget(QLabel("Email:"))
        form_layout.addWidget(self.client_email_input)
        form_layout.addWidget(QLabel("Address:"))
        form_layout.addWidget(self.client_address_input)
        form_layout.addWidget(self.add_client_button)

        layout.addLayout(form_layout)

        # List of clients
        self.clients_list = QListWidget()
        self.load_clients()
        layout.addWidget(QLabel("Existing Clients:"))
        layout.addWidget(self.clients_list)

        # Buttons to edit and delete clients
        btn_layout = QHBoxLayout()
        self.edit_client_button = QPushButton("Edit Selected")
        self.edit_client_button.clicked.connect(self.edit_client)
        self.delete_client_button = QPushButton("Delete Selected")
        self.delete_client_button.clicked.connect(self.delete_client)
        btn_layout.addWidget(self.edit_client_button)
        btn_layout.addWidget(self.delete_client_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_clients(self):
        self.clients_list.clear()
        for client in self.parent.clients.keys():
            item = QListWidgetItem(client)
            self.clients_list.addItem(item)

    def add_client(self):
        name = self.client_name_input.text().strip()
        email = self.client_email_input.text().strip()
        address = self.client_address_input.text().strip()

        if not name or not email or not address:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        if name in self.parent.clients:
            QMessageBox.warning(self, "Duplicate Client", "Client already exists.")
            return

        self.parent.clients[name] = {
            "email": email,
            "address": address
        }
        save_json(self.parent.clients, CLIENTS_FILE)
        self.parent.log_message(f"Client '{name}' added.")
        self.load_clients()
        self.client_name_input.clear()
        self.client_email_input.clear()
        self.client_address_input.clear()

    def edit_client(self):
        selected = self.clients_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a client to edit.")
            return
        name = selected.text()
        client_data = self.parent.clients[name]

        # Dialog to edit client
        dialog = EditClientDialog(name, client_data, self.parent)
        if dialog.exec():
            new_name, new_email, new_address = dialog.get_data()
            if new_name != name and new_name in self.parent.clients:
                QMessageBox.warning(self, "Duplicate Client", "Client name already exists.")
                return
            # Update client
            del self.parent.clients[name]
            self.parent.clients[new_name] = {
                "email": new_email,
                "address": new_address
            }
            save_json(self.parent.clients, CLIENTS_FILE)
            self.parent.log_message(f"Client '{name}' updated to '{new_name}'.")
            self.load_clients()

    def delete_client(self):
        selected = self.clients_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a client to delete.")
            return
        name = selected.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete client '{name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.clients[name]
            save_json(self.parent.clients, CLIENTS_FILE)
            self.parent.log_message(f"Client '{name}' deleted.")
            self.load_clients()

class EditClientDialog(QtWidgets.QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Client")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.email_input = QLineEdit(self)
        self.email_input.setText(self.data["email"])
        self.address_input = QLineEdit(self)
        self.address_input.setText(self.data["address"])

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Email:"))
        self.layout.addWidget(self.email_input)
        self.layout.addWidget(QLabel("Address:"))
        self.layout.addWidget(self.address_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.email_input.text().strip(),
            self.address_input.text().strip()
        )

class LineItemManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Form to add new line item
        form_layout = QHBoxLayout()
        self.item_name_input = QLineEdit(self)
        self.item_name_input.setPlaceholderText("Item Name")
        self.item_price_input = QLineEdit(self)
        self.item_price_input.setPlaceholderText("Item Price")
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)

        form_layout.addWidget(QLabel("Name:"))
        form_layout.addWidget(self.item_name_input)
        form_layout.addWidget(QLabel("Price:"))
        form_layout.addWidget(self.item_price_input)
        form_layout.addWidget(self.add_item_button)

        layout.addLayout(form_layout)

        # List of line items
        self.items_list = QListWidget()
        self.load_line_items()
        layout.addWidget(QLabel("Existing Line Items:"))
        layout.addWidget(self.items_list)

        # Buttons to edit and delete items
        btn_layout = QHBoxLayout()
        self.edit_item_button = QPushButton("Edit Selected")
        self.edit_item_button.clicked.connect(self.edit_line_item)
        self.delete_item_button = QPushButton("Delete Selected")
        self.delete_item_button.clicked.connect(self.delete_line_item)
        btn_layout.addWidget(self.edit_item_button)
        btn_layout.addWidget(self.delete_item_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_line_items(self):
        self.items_list.clear()
        for item in self.parent.line_items_data.keys():
            item_obj = QListWidgetItem(item)
            self.items_list.addItem(item_obj)

    def add_line_item(self):
        name = self.item_name_input.text().strip()
        price = self.item_price_input.text().strip()

        if not name or not price:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        try:
            price = float(price)
            if price < 0:
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "Input Error", "Please enter a valid positive price.")
            return

        if name in self.parent.line_items_data:
            QMessageBox.warning(self, "Duplicate Item", "Line item already exists.")
            return

        self.parent.line_items_data[name] = {"price": price}
        save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
        self.parent.log_message(f"Line item '{name}' added.")
        self.load_line_items()
        self.item_name_input.clear()
        self.item_price_input.clear()

    def edit_line_item(self):
        selected = self.items_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a line item to edit.")
            return
        name = selected.text()
        item_data = self.parent.line_items_data[name]

        # Dialog to edit line item
        dialog = EditLineItemDialog(name, item_data, self.parent)
        if dialog.exec():
            new_name, new_price = dialog.get_data()
            if new_name != name and new_name in self.parent.line_items_data:
                QMessageBox.warning(self, "Duplicate Item", "Line item name already exists.")
                return
            try:
                new_price = float(new_price)
                if new_price < 0:
                    raise ValueError
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid positive price.")
                return
            # Update line item
            del self.parent.line_items_data[name]
            self.parent.line_items_data[new_name] = {"price": new_price}
            save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
            self.parent.log_message(f"Line item '{name}' updated to '{new_name}'.")
            self.load_line_items()

    def delete_line_item(self):
        selected = self.items_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a line item to delete.")
            return
        name = selected.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete line item '{name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.line_items_data[name]
            save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
            self.parent.log_message(f"Line item '{name}' deleted.")
            self.load_line_items()

class EditLineItemDialog(QtWidgets.QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Line Item")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.price_input = QLineEdit(self)
        self.price_input.setText(str(self.data["price"]))

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Price:"))
        self.layout.addWidget(self.price_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.price_input.text().strip()
        )

class InvoiceCreationTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.line_items = []
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Client selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # Add VAT field (optional)
        vat_layout = QHBoxLayout()
        self.vat_input = QLineEdit(self)
        self.vat_input.setPlaceholderText("VAT Number (Optional)")
        vat_layout.addWidget(QLabel("VAT Number:"))
        vat_layout.addWidget(self.vat_input)
        layout.addLayout(vat_layout)

        # Line Items selection
        line_item_layout = QHBoxLayout()
        self.item_combo = QComboBox(self)
        self.load_line_items()
        self.quantity_input = QSpinBox(self)
        self.quantity_input.setMinimum(1)
        self.quantity_input.setValue(1)
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)
        line_item_layout.addWidget(QLabel("Item:"))
        line_item_layout.addWidget(self.item_combo)
        line_item_layout.addWidget(QLabel("Quantity:"))
        line_item_layout.addWidget(self.quantity_input)
        line_item_layout.addWidget(self.add_item_button)
        layout.addLayout(line_item_layout)

        # List to display added line items
        self.added_items_list = QListWidget()
        self.added_items_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        layout.addWidget(QLabel("Added Line Items:"))
        layout.addWidget(self.added_items_list)

        # Discounts and Taxes
        discounts_taxes_layout = QHBoxLayout()
        self.discount_input = QLineEdit(self)
        self.discount_input.setPlaceholderText("Discount (%)")
        self.tax_rate_input = QLineEdit(self)
        self.tax_rate_input.setPlaceholderText("Tax Rate (%)")
        discounts_taxes_layout.addWidget(QLabel("Discount (%):"))
        discounts_taxes_layout.addWidget(self.discount_input)
        discounts_taxes_layout.addWidget(QLabel("Tax Rate (%):"))
        discounts_taxes_layout.addWidget(self.tax_rate_input)
        layout.addLayout(discounts_taxes_layout)

        # Output Format Selection
        output_layout = QHBoxLayout()
        self.output_format_combo = QComboBox(self)
        self.output_format_combo.addItems(["PDF", "Excel"])
        output_layout.addWidget(QLabel("Output Format:"))
        output_layout.addWidget(self.output_format_combo)
        layout.addLayout(output_layout)

        # Button to create invoice
        self.create_invoice_button = QPushButton("Create Invoice")
        self.create_invoice_button.clicked.connect(self.create_invoice)
        layout.addWidget(self.create_invoice_button)

        self.setLayout(layout)

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_line_items(self):
        self.item_combo.clear()
        self.item_combo.addItems(list(self.parent.line_items_data.keys()))

    def add_line_item(self):
        item_name = self.item_combo.currentText()
        quantity = self.quantity_input.value()
        if not item_name:
            QMessageBox.warning(self, "Selection Error", "Please select a line item.")
            return
        price = self.parent.line_items_data[item_name]['price']
        total = price * quantity
        self.line_items.append({
            "item": item_name,
            "quantity": quantity,
            "price": price,
            "total": total
        })
        self.update_added_items_list()

    def update_added_items_list(self):
        self.added_items_list.clear()
        for idx, item in enumerate(self.line_items, 1):
            self.added_items_list.addItem(
                f"{idx}. {item['item']} - Qty: {item['quantity']} - Price: R{item['price']:.2f} - Total: R{item['total']:.2f}"
            )

    def create_invoice(self):
        client_name = self.client_combo.currentText()
        vat_number = self.vat_input.text().strip()
        discount = self.discount_input.text().strip()
        tax_rate = self.tax_rate_input.text().strip()

        if not client_name:
            QMessageBox.warning(self, "Input Error", "Please select a client.")
            return

        if not self.line_items:
            QMessageBox.warning(self, "Input Error", "Please add at least one line item.")
            return

        # Calculate totals
        subtotal = sum(item['total'] for item in self.line_items)
        discount_amount = 0
        if discount:
            try:
                discount_percentage = float(discount)
                discount_amount = subtotal * (discount_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid discount percentage.")
                return

        subtotal_after_discount = subtotal - discount_amount

        tax_amount = 0
        if tax_rate:
            try:
                tax_percentage = float(tax_rate)
                tax_amount = subtotal_after_discount * (tax_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid tax rate percentage.")
                return

        total = subtotal_after_discount + tax_amount

        invoice_date = datetime.now().strftime("%Y-%m-%d")
        client_address = self.parent.clients[client_name]["address"]
        invoice_filename = f"{client_name.replace(' ', '_')}_invoice_{self.parent.invoice_counter}"

        # Prepare DataFrame
        df = pd.DataFrame([
            {"Item": item['item'], "Quantity": item['quantity'], "Price": item['price'], "Total": item['total']}
            for item in self.line_items
        ])

        output_format = self.output_format_combo.currentText()
        if output_format == "PDF":
            self.create_pdf(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        elif output_format == "Excel":
            self.create_excel(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        else:
            QMessageBox.warning(self, "Format Error", "Please select a valid output format.")

        # Reset form
        self.line_items = []
        self.update_added_items_list()
        self.parent.invoice_counter += 1  # Increment invoice counter after successful creation
        self.parent.update_invoice_counter()

    def create_pdf(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate PDF invoice."""
        pdf_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.pdf")
        c = canvas.Canvas(pdf_filename, pagesize=letter)
        width, height = letter

        # Invoice Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(50, height - 50, "INVOICE")

        # Client Information
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 100, f"Invoice Number: {self.parent.invoice_counter - 1}")  # Displaying counter
        c.drawString(50, height - 120, f"Date: {invoice_date}")
        c.drawString(50, height - 140, f"Client: {client}")
        c.drawString(50, height - 160, f"Address: {client_address}")
        if vat:
            c.drawString(50, height - 180, f"VAT Number: {vat}")

        # Table Headers
        c.setFont("Helvetica-Bold", 12)
        y = height - 220
        c.drawString(50, y, "Item")
        c.drawString(250, y, "Quantity")
        c.drawString(350, y, "Price (ZAR)")
        c.drawString(450, y, "Total (ZAR)")
        y -= 20
        c.line(50, y, 550, y)

        # Table Content
        c.setFont("Helvetica", 12)
        for index, row in df.iterrows():
            y -= 20
            if y < 50:
                c.showPage()
                y = height - 50
            c.drawString(50, y, str(row['Item']))
            c.drawString(250, y, str(row['Quantity']))
            c.drawString(350, y, f"R{row['Price']:.2f}")
            c.drawString(450, y, f"R{row['Total']:.2f}")

        # Totals
        y -= 40
        c.drawString(350, y, "Subtotal:")
        c.drawString(450, y, f"R{subtotal:.2f}")
        y -= 20
        if discount > 0:
            c.drawString(350, y, "Discount:")
            c.drawString(450, y, f"R{-discount:.2f}")
            y -= 20
        if tax > 0:
            c.drawString(350, y, "Tax:")
            c.drawString(450, y, f"R{tax:.2f}")
            y -= 20
        c.setFont("Helvetica-Bold", 12)
        c.drawString(350, y, "Total:")
        c.drawString(450, y, f"R{total:.2f}")

        c.save()
        self.parent.log_message(f"Invoice saved as {pdf_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.send_email_invoice(client, pdf_filename)

    def create_excel(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate Excel invoice."""
        excel_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.xlsx")
        writer = pd.ExcelWriter(excel_filename, engine='openpyxl')
        df.to_excel(writer, index=False, sheet_name='Invoice')

        # Access the workbook and worksheet
        workbook = writer.book
        worksheet = writer.sheets['Invoice']

        # Add header information
        worksheet.cell(row=1, column=1, value="Invoice Number:")
        worksheet.cell(row=1, column=2, value=self.parent.invoice_counter - 1)
        worksheet.cell(row=2, column=1, value="Invoice Date:")
        worksheet.cell(row=2, column=2, value=invoice_date)
        worksheet.cell(row=3, column=1, value="Client:")
        worksheet.cell(row=3, column=2, value=client)
        worksheet.cell(row=4, column=1, value="Address:")
        worksheet.cell(row=4, column=2, value=client_address)
        if vat:
            worksheet.cell(row=5, column=1, value="VAT Number:")
            worksheet.cell(row=5, column=2, value=vat)

        # Add totals
        start_row = len(df) + 7
        worksheet.cell(row=start_row, column=3, value="Subtotal:")
        worksheet.cell(row=start_row, column=4, value=subtotal)
        if discount > 0:
            worksheet.cell(row=start_row + 1, column=3, value="Discount:")
            worksheet.cell(row=start_row + 1, column=4, value=-discount)
        if tax > 0:
            worksheet.cell(row=start_row + 2, column=3, value="Tax:")
            worksheet.cell(row=start_row + 2, column=4, value=tax)
        worksheet.cell(row=start_row + 3, column=3, value="Total:")
        worksheet.cell(row=start_row + 3, column=4, value=total)

        writer.save()
        self.parent.log_message(f"Invoice saved as {excel_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.send_email_invoice(client, excel_filename)

    def send_email_invoice(self, client, invoice_path):
        """Send the invoice via email."""
        # Load email configuration
        email_config = self.parent.email_config
        if not email_config:
            QMessageBox.warning(self, "Email Configuration Missing", "Please configure email settings in the 'Email Settings' tab.")
            return

        sender_email = email_config.get("sender_email")
        sender_password = email_config.get("sender_password")
        smtp_server = email_config.get("smtp_server")
        smtp_port = email_config.get("smtp_port")
        if not all([sender_email, sender_password, smtp_server, smtp_port]):
            QMessageBox.warning(self, "Incomplete Email Configuration", "Please complete all email settings.")
            return

        recipient_email = self.parent.clients[client]['email']
        subject = f"Invoice from QuickBooks Replacement Tool"
        body = f"Dear {client},\n\nPlease find attached your invoice.\n\nThank you!"

        # Create the email message
        msg = MIMEText(body)
        msg['Subject'] = subject
        msg['From'] = sender_email
        msg['To'] = recipient_email

        # Attach the invoice file
        with open(invoice_path, 'rb') as f:
            part = MIMEApplication(f.read(), Name=os.path.basename(invoice_path))
        part['Content-Disposition'] = f'attachment; filename="{os.path.basename(invoice_path)}"'
        msg.attach(part)

        try:
            with smtplib.SMTP(smtp_server, smtp_port) as server:
                server.starttls()
                server.login(sender_email, sender_password)
                server.send_message(msg)
            self.parent.log_message(f"Invoice sent to {client} at {recipient_email}.")
            QMessageBox.information(self, "Email Sent", f"Invoice has been sent to {recipient_email}.")
        except Exception as e:
            QMessageBox.warning(self, "Email Error", f"Failed to send email: {e}")
            self.parent.log_message(f"Failed to send invoice to {client}: {e}")


class ManageInvoicesTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # List of invoices
        self.invoices_list = QListWidget()
        self.invoices_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        self.load_invoices()
        layout.addWidget(QLabel("Invoices:"))
        layout.addWidget(self.invoices_list)

        # Buttons to view, resend, and delete invoices
        btn_layout = QHBoxLayout()
        self.view_invoice_button = QPushButton("View Invoice")
        self.view_invoice_button.clicked.connect(self.view_invoice)
        self.resend_invoice_button = QPushButton("Resend Invoice")
        self.resend_invoice_button.clicked.connect(self.resend_invoice)
        self.delete_invoice_button = QPushButton("Delete Invoice")
        self.delete_invoice_button.clicked.connect(self.delete_invoice)
        btn_layout.addWidget(self.view_invoice_button)
        btn_layout.addWidget(self.resend_invoice_button)
        btn_layout.addWidget(self.delete_invoice_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_invoices(self):
        self.invoices_list.clear()
        if not os.path.exists(INVOICES_DIR):
            return
        invoices = sorted(os.listdir(INVOICES_DIR), reverse=True)
        for invoice in invoices:
            item = QListWidgetItem(invoice)
            self.invoices_list.addItem(item)

    def get_selected_invoice_path(self):
        selected = self.invoices_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice.")
            return None
        invoice_filename = selected.text()
        invoice_path = os.path.join(INVOICES_DIR, invoice_filename)
        return invoice_path

    def view_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        if not os.path.exists(invoice_path):
            QMessageBox.warning(self, "File Not Found", "The selected invoice file does not exist.")
            return
        QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(os.path.abspath(invoice_path)))

    def resend_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        # Extract client name from filename
        try:
            client_name = invoice_path.split('_invoice_')[0].replace('_', ' ')
            if client_name not in self.parent.clients:
                raise ValueError("Client not found.")
            self.parent.invoice_tab.send_email_invoice(client_name, invoice_path)
        except Exception as e:
            QMessageBox.warning(self, "Resend Error", f"Failed to resend invoice: {e}")
            self.parent.log_message(f"Failed to resend invoice '{invoice_path}': {e}")

    def delete_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete invoice '{os.path.basename(invoice_path)}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                os.remove(invoice_path)
                self.parent.log_message(f"Invoice '{os.path.basename(invoice_path)}' deleted.")
                self.load_invoices()
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete invoice: {e}")
                self.parent.log_message(f"Failed to delete invoice '{invoice_path}': {e}")

class EmailConfigTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Email configuration form
        self.sender_email_input = QLineEdit(self)
        self.sender_email_input.setPlaceholderText("Sender Email")
        self.sender_password_input = QLineEdit(self)
        self.sender_password_input.setPlaceholderText("Sender Password")
        self.sender_password_input.setEchoMode(QLineEdit.EchoMode.Password)
        self.smtp_server_input = QLineEdit(self)
        self.smtp_server_input.setPlaceholderText("SMTP Server (e.g., smtp.office365.com)")
        self.smtp_port_input = QLineEdit(self)
        self.smtp_port_input.setPlaceholderText("SMTP Port (e.g., 587)")

        form_layout = QVBoxLayout()
        form_layout.addWidget(QLabel("Sender Email:"))
        form_layout.addWidget(self.sender_email_input)
        form_layout.addWidget(QLabel("Sender Password:"))
        form_layout.addWidget(self.sender_password_input)
        form_layout.addWidget(QLabel("SMTP Server:"))
        form_layout.addWidget(self.smtp_server_input)
        form_layout.addWidget(QLabel("SMTP Port:"))
        form_layout.addWidget(self.smtp_port_input)

        layout.addLayout(form_layout)

        # Save button
        self.save_email_config_button = QPushButton("Save Email Settings")
        self.save_email_config_button.clicked.connect(self.save_email_config)
        layout.addWidget(self.save_email_config_button)

        self.setLayout(layout)
        self.load_existing_config()

    def load_existing_config(self):
        config = self.parent.email_config
        if config:
            self.sender_email_input.setText(config.get("sender_email", ""))
            self.sender_password_input.setText(config.get("sender_password", ""))
            self.smtp_server_input.setText(config.get("smtp_server", ""))
            self.smtp_port_input.setText(str(config.get("smtp_port", "")))

    def save_email_config(self):
        sender_email = self.sender_email_input.text().strip()
        sender_password = self.sender_password_input.text().strip()
        smtp_server = self.smtp_server_input.text().strip()
        smtp_port = self.smtp_port_input.text().strip()

        if not sender_email or not sender_password or not smtp_server or not smtp_port:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        try:
            smtp_port = int(smtp_port)
        except ValueError:
            QMessageBox.warning(self, "Input Error", "SMTP Port must be a number.")
            return

        self.parent.email_config = {
            "sender_email": sender_email,
            "sender_password": sender_password,
            "smtp_server": smtp_server,
            "smtp_port": smtp_port
        }
        save_json(self.parent.email_config, EMAIL_CONFIG_FILE)
        self.parent.log_message("Email settings updated.")
        QMessageBox.information(self, "Success", "Email settings have been saved.")


# Run the application
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = QuickBooksReplacement()
    window.show()
    sys.exit(app.exec())