commit 30b1c5b775d9afe5a70784151a819e2f55a62bf5 Author: godax84 Date: Thu Dec 19 09:03:07 2024 -0800 Carregar ficheiros para "/" diff --git a/backup_manager.py b/backup_manager.py new file mode 100644 index 0000000..7f3ecd7 --- /dev/null +++ b/backup_manager.py @@ -0,0 +1,296 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QProgressDialog, QFileDialog, QMessageBox) +from PySide6.QtCore import Qt +from database import get_session +from models import Document, DocumentVersion, Folder, Tag +import os +import json +import shutil +import zipfile +from datetime import datetime + +class BackupManager(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Export button + self.export_btn = QPushButton("Exportar Documentos") + self.export_btn.clicked.connect(self.export_documents) + layout.addWidget(self.export_btn) + + # Import button + self.import_btn = QPushButton("Importar Documentos") + self.import_btn.clicked.connect(self.import_documents) + layout.addWidget(self.import_btn) + + def export_documents(self): + export_dir = QFileDialog.getExistingDirectory( + self, + "Selecionar Pasta para Exportação" + ) + + if not export_dir: + return + + try: + session = get_session() + + # Create export directory structure + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + export_path = os.path.join(export_dir, f"doc_export_{timestamp}") + os.makedirs(export_path) + + # Create progress dialog + documents = session.query(Document).all() + progress = QProgressDialog( + "Exportando documentos...", + "Cancelar", + 0, + len(documents), + self + ) + progress.setWindowModality(Qt.WindowModal) + + # Export metadata + metadata = { + 'documents': [], + 'folders': [], + 'tags': [] + } + + # Export documents and their versions + for i, doc in enumerate(documents): + if progress.wasCanceled(): + break + + # Create document directory + doc_dir = os.path.join(export_path, f"doc_{doc.id}") + os.makedirs(doc_dir) + + # Copy main document + if os.path.exists(doc.file_path): + shutil.copy2(doc.file_path, + os.path.join(doc_dir, os.path.basename(doc.file_path))) + + # Copy versions + versions_dir = os.path.join(doc_dir, "versions") + os.makedirs(versions_dir) + for version in doc.versions: + if os.path.exists(version.file_path): + shutil.copy2( + version.file_path, + os.path.join(versions_dir, os.path.basename(version.file_path)) + ) + + # Add document metadata + doc_data = { + 'id': doc.id, + 'file_name': doc.file_name, + 'marca': doc.marca, + 'modelo': doc.modelo, + 'ano': doc.ano, + 'cilindrada': doc.cilindrada, + 'codigo_motor': doc.codigo_motor, + 'tipo_documento': doc.tipo_documento, + 'variante': doc.variante, + 'observacoes': doc.observacoes, + 'folder_id': doc.folder_id, + 'tags': [tag.name for tag in doc.tags], + 'versions': [{ + 'version_number': v.version_number, + 'file_name': os.path.basename(v.file_path), + 'changes': v.changes, + 'created_at': v.created_at.isoformat() + } for v in doc.versions] + } + metadata['documents'].append(doc_data) + + progress.setValue(i + 1) + + # Export folders + folders = session.query(Folder).all() + for folder in folders: + folder_data = { + 'id': folder.id, + 'name': folder.name, + 'parent_id': folder.parent_id + } + metadata['folders'].append(folder_data) + + # Export tags + tags = session.query(Tag).all() + for tag in tags: + tag_data = { + 'name': tag.name, + 'color': tag.color + } + metadata['tags'].append(tag_data) + + # Save metadata + with open(os.path.join(export_path, 'metadata.json'), 'w', + encoding='utf-8') as f: + json.dump(metadata, f, ensure_ascii=False, indent=2) + + # Create ZIP archive + zip_path = f"{export_path}.zip" + with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: + for root, _, files in os.walk(export_path): + for file in files: + file_path = os.path.join(root, file) + arcname = os.path.relpath(file_path, export_path) + zipf.write(file_path, arcname) + + # Clean up temporary directory + shutil.rmtree(export_path) + + QMessageBox.information( + self, + "Sucesso", + f"Exportação concluída com sucesso!\nArquivo: {zip_path}" + ) + + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro durante a exportação: {str(e)}") + finally: + session.close() + + def import_documents(self): + zip_path, _ = QFileDialog.getOpenFileName( + self, + "Selecionar Arquivo de Importação", + "", + "Arquivos ZIP (*.zip)" + ) + + if not zip_path: + return + + try: + session = get_session() + + # Create temporary directory for extraction + temp_dir = os.path.join(os.path.dirname(zip_path), "temp_import") + os.makedirs(temp_dir, exist_ok=True) + + # Extract ZIP file + with zipfile.ZipFile(zip_path, 'r') as zipf: + zipf.extractall(temp_dir) + + # Read metadata + with open(os.path.join(temp_dir, 'metadata.json'), 'r', + encoding='utf-8') as f: + metadata = json.load(f) + + # Import tags + for tag_data in metadata['tags']: + if not session.query(Tag)\ + .filter(Tag.name == tag_data['name']).first(): + tag = Tag( + name=tag_data['name'], + color=tag_data['color'] + ) + session.add(tag) + + # Import folders + for folder_data in metadata['folders']: + if not session.query(Folder)\ + .filter(Folder.id == folder_data['id']).first(): + folder = Folder( + id=folder_data['id'], + name=folder_data['name'], + parent_id=folder_data['parent_id'] + ) + session.add(folder) + + # Create progress dialog + progress = QProgressDialog( + "Importando documentos...", + "Cancelar", + 0, + len(metadata['documents']), + self + ) + progress.setWindowModality(Qt.WindowModal) + + # Import documents + for i, doc_data in enumerate(metadata['documents']): + if progress.wasCanceled(): + break + + # Check if document already exists + existing_doc = session.query(Document)\ + .filter(Document.file_name == doc_data['file_name'])\ + .filter(Document.folder_id == doc_data['folder_id'])\ + .first() + + if not existing_doc: + # Copy document file + doc_dir = os.path.join(temp_dir, f"doc_{doc_data['id']}") + original_file = os.path.join(doc_dir, doc_data['file_name']) + + if os.path.exists(original_file): + # Create new document + new_doc = Document( + file_name=doc_data['file_name'], + file_path=original_file, + marca=doc_data['marca'], + modelo=doc_data['modelo'], + ano=doc_data['ano'], + cilindrada=doc_data['cilindrada'], + codigo_motor=doc_data['codigo_motor'], + tipo_documento=doc_data['tipo_documento'], + variante=doc_data['variante'], + observacoes=doc_data['observacoes'], + folder_id=doc_data['folder_id'] + ) + + # Add tags + for tag_name in doc_data['tags']: + tag = session.query(Tag)\ + .filter(Tag.name == tag_name).first() + if tag: + new_doc.tags.append(tag) + + session.add(new_doc) + session.flush() # Get new_doc.id + + # Import versions + versions_dir = os.path.join(doc_dir, "versions") + if os.path.exists(versions_dir): + for version_data in doc_data['versions']: + version_file = os.path.join( + versions_dir, + version_data['file_name'] + ) + if os.path.exists(version_file): + new_version = DocumentVersion( + document_id=new_doc.id, + version_number=version_data['version_number'], + file_path=version_file, + changes=version_data['changes'], + created_at=datetime.fromisoformat( + version_data['created_at']) + ) + session.add(new_version) + + progress.setValue(i + 1) + + session.commit() + + # Clean up + shutil.rmtree(temp_dir) + + QMessageBox.information(self, "Sucesso", + "Importação concluída com sucesso!") + + except Exception as e: + session.rollback() + QMessageBox.critical(self, "Erro", + f"Erro durante a importação: {str(e)}") + finally: + session.close() diff --git a/batch_upload.py b/batch_upload.py new file mode 100644 index 0000000..4c9b3a0 --- /dev/null +++ b/batch_upload.py @@ -0,0 +1,144 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QTableWidget, QTableWidgetItem, QHeaderView, + QFileDialog, QMessageBox, QProgressDialog) +from PySide6.QtCore import Qt, Signal +import os +from database import get_session +from models import Document +from document_form import DocumentForm + +class BatchUploadWidget(QWidget): + upload_completed = Signal() + + def __init__(self, parent=None): + super().__init__(parent) + self.files_to_upload = [] + self.folder_id = None + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Buttons + button_layout = QHBoxLayout() + self.add_files_btn = QPushButton("Adicionar Arquivos") + self.add_files_btn.clicked.connect(self.add_files) + self.remove_files_btn = QPushButton("Remover Selecionados") + self.remove_files_btn.clicked.connect(self.remove_selected_files) + self.upload_all_btn = QPushButton("Upload em Lote") + self.upload_all_btn.clicked.connect(self.upload_all) + + button_layout.addWidget(self.add_files_btn) + button_layout.addWidget(self.remove_files_btn) + button_layout.addWidget(self.upload_all_btn) + layout.addLayout(button_layout) + + # Files table + self.files_table = QTableWidget() + self.files_table.setColumnCount(2) + self.files_table.setHorizontalHeaderLabels(["Nome do Arquivo", "Caminho"]) + self.files_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + layout.addWidget(self.files_table) + + # Create document form for metadata + self.doc_form = DocumentForm() + layout.addWidget(self.doc_form) + + def add_files(self): + files, _ = QFileDialog.getOpenFileNames( + self, + "Selecionar Arquivos", + "", + "Todos os Arquivos (*);;PDF (*.pdf);;Imagens (*.png *.jpg);;Documentos (*.doc *.docx)" + ) + + for file_path in files: + if file_path not in [self.files_table.item(row, 1).text() + for row in range(self.files_table.rowCount())]: + row = self.files_table.rowCount() + self.files_table.insertRow(row) + self.files_table.setItem(row, 0, QTableWidgetItem(os.path.basename(file_path))) + self.files_table.setItem(row, 1, QTableWidgetItem(file_path)) + + def remove_selected_files(self): + rows = set(item.row() for item in self.files_table.selectedItems()) + for row in sorted(rows, reverse=True): + self.files_table.removeRow(row) + + def upload_all(self): + if not self.validate_form(): + return + + progress = QProgressDialog( + "Fazendo upload dos arquivos...", + "Cancelar", + 0, + self.files_table.rowCount(), + self + ) + progress.setWindowModality(Qt.WindowModal) + + try: + session = get_session() + + for row in range(self.files_table.rowCount()): + if progress.wasCanceled(): + break + + file_path = self.files_table.item(row, 1).text() + + # Create new document with metadata + new_doc = Document( + file_path=file_path, + file_name=os.path.basename(file_path), + marca=self.doc_form.marca_edit.text(), + modelo=self.doc_form.modelo_edit.text(), + ano=self.doc_form.ano_spin.value(), + cilindrada=self.doc_form.cilindrada_edit.text(), + codigo_motor=self.doc_form.codigo_motor_edit.text(), + tipo_documento=self.doc_form.tipo_combo.currentText(), + variante=self.doc_form.variante_combo.currentText(), + observacoes=self.doc_form.observacoes_edit.toPlainText(), + folder_id=self.folder_id + ) + + session.add(new_doc) + progress.setValue(row + 1) + + session.commit() + session.close() + + QMessageBox.information(self, "Sucesso", + "Upload em lote concluído com sucesso!") + self.clear_form() + self.upload_completed.emit() + + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro durante o upload em lote: {str(e)}") + session.rollback() + session.close() + + progress.close() + + def validate_form(self): + if self.files_table.rowCount() == 0: + QMessageBox.warning(self, "Validação", + "Adicione pelo menos um arquivo.") + return False + + if not self.doc_form.marca_edit.text(): + QMessageBox.warning(self, "Validação", + "Por favor, preencha a marca.") + return False + + if not self.doc_form.modelo_edit.text(): + QMessageBox.warning(self, "Validação", + "Por favor, preencha o modelo.") + return False + + return True + + def clear_form(self): + self.files_table.setRowCount(0) + self.doc_form.clear_form() diff --git a/comments.py b/comments.py new file mode 100644 index 0000000..90f2cb1 --- /dev/null +++ b/comments.py @@ -0,0 +1,205 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QTextEdit, QListWidget, QListWidgetItem, + QLabel, QMenu, QMessageBox, QDialog) +from PySide6.QtCore import Qt, Signal, QPoint +from PySide6.QtGui import QAction +from database import get_session +from models import Comment, Document +from datetime import datetime + +class CommentWidget(QWidget): + comment_updated = Signal() + + def __init__(self, comment, parent=None): + super().__init__(parent) + self.comment = comment + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + + # Header with timestamp and options + header_layout = QHBoxLayout() + + timestamp = QLabel(self.comment.created_at.strftime("%d/%m/%Y %H:%M")) + timestamp.setStyleSheet("color: gray; font-size: 10px;") + header_layout.addWidget(timestamp) + + if self.comment.updated_at and self.comment.updated_at != self.comment.created_at: + edited_label = QLabel("(editado)") + edited_label.setStyleSheet("color: gray; font-size: 10px;") + header_layout.addWidget(edited_label) + + header_layout.addStretch() + + # Options button + options_btn = QPushButton("...") + options_btn.setFixedWidth(30) + options_btn.clicked.connect(self.show_options) + header_layout.addWidget(options_btn) + + layout.addLayout(header_layout) + + # Comment text + text_label = QLabel(self.comment.text) + text_label.setWordWrap(True) + layout.addWidget(text_label) + + # Location info if available + if self.comment.page_number: + location = f"Página {self.comment.page_number}" + if self.comment.x_coord and self.comment.y_coord: + location += f" (x: {self.comment.x_coord}, y: {self.comment.y_coord})" + + location_label = QLabel(location) + location_label.setStyleSheet("color: gray; font-style: italic; font-size: 10px;") + layout.addWidget(location_label) + + def show_options(self): + menu = QMenu(self) + + edit_action = QAction("Editar", self) + edit_action.triggered.connect(self.edit_comment) + menu.addAction(edit_action) + + delete_action = QAction("Excluir", self) + delete_action.triggered.connect(self.delete_comment) + menu.addAction(delete_action) + + # Show menu at button position + button = self.sender() + pos = button.mapToGlobal(QPoint(0, button.height())) + menu.exec_(pos) + + def edit_comment(self): + dialog = CommentDialog(self.comment.text, self) + if dialog.exec_(): + try: + session = get_session() + comment = session.query(Comment).get(self.comment.id) + comment.text = dialog.comment_text() + comment.updated_at = datetime.utcnow() + session.commit() + self.comment_updated.emit() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao editar comentário: {str(e)}") + finally: + session.close() + + def delete_comment(self): + reply = QMessageBox.question( + self, + "Confirmar Exclusão", + "Tem certeza que deseja excluir este comentário?", + QMessageBox.Yes | QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + session = get_session() + comment = session.query(Comment).get(self.comment.id) + session.delete(comment) + session.commit() + self.comment_updated.emit() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao excluir comentário: {str(e)}") + finally: + session.close() + +class CommentsList(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.current_document = None + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Header + header_layout = QHBoxLayout() + header_layout.addWidget(QLabel("Comentários")) + + self.add_comment_btn = QPushButton("Novo Comentário") + self.add_comment_btn.clicked.connect(self.add_comment) + header_layout.addWidget(self.add_comment_btn) + + layout.addLayout(header_layout) + + # Comments list + self.comments_list = QListWidget() + layout.addWidget(self.comments_list) + + def load_document(self, doc_id): + self.current_document = doc_id + self.refresh_comments() + + def refresh_comments(self): + self.comments_list.clear() + + if self.current_document: + session = get_session() + document = session.query(Document).get(self.current_document) + + for comment in document.comments: + item = QListWidgetItem() + widget = CommentWidget(comment) + widget.comment_updated.connect(self.refresh_comments) + + item.setSizeHint(widget.sizeHint()) + self.comments_list.addItem(item) + self.comments_list.setItemWidget(item, widget) + + session.close() + + def add_comment(self): + if not self.current_document: + return + + dialog = CommentDialog(parent=self) + if dialog.exec_(): + try: + session = get_session() + comment = Comment( + document_id=self.current_document, + text=dialog.comment_text() + ) + session.add(comment) + session.commit() + self.refresh_comments() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao adicionar comentário: {str(e)}") + finally: + session.close() + +class CommentDialog(QDialog): + def __init__(self, initial_text="", parent=None): + super().__init__(parent) + self.initial_text = initial_text + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("Comentário") + layout = QVBoxLayout(self) + + self.text_edit = QTextEdit() + self.text_edit.setPlainText(self.initial_text) + layout.addWidget(self.text_edit) + + buttons_layout = QHBoxLayout() + + cancel_btn = QPushButton("Cancelar") + cancel_btn.clicked.connect(self.reject) + buttons_layout.addWidget(cancel_btn) + + save_btn = QPushButton("Salvar") + save_btn.clicked.connect(self.accept) + buttons_layout.addWidget(save_btn) + + layout.addLayout(buttons_layout) + + def comment_text(self): + return self.text_edit.toPlainText() diff --git a/database.py b/database.py new file mode 100644 index 0000000..8cc5487 --- /dev/null +++ b/database.py @@ -0,0 +1,23 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from models import Base +import os + +# Get the absolute path to the database file +DATABASE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "automotive_docs.db") +DATABASE_URL = f"sqlite:///{DATABASE_FILE}" + +def init_db(): + # Create new engine + engine = create_engine(DATABASE_URL) + + # Create tables if they don't exist + if not os.path.exists(DATABASE_FILE): + Base.metadata.create_all(engine) + + return engine + +def get_session(): + engine = create_engine(DATABASE_URL) + Session = sessionmaker(bind=engine) + return Session() diff --git a/document_collections.py b/document_collections.py new file mode 100644 index 0000000..3da7737 --- /dev/null +++ b/document_collections.py @@ -0,0 +1,373 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton, + QListWidget, QListWidgetItem, QInputDialog, + QMessageBox, QMenu, QDialog, QLabel, QLineEdit, + QTextEdit, QComboBox) +from PySide6.QtCore import Qt, Signal +from PySide6.QtGui import QAction, QColor, QIcon +from database import get_session +from models import Collection, Document +import json + +class CollectionDialog(QDialog): + def __init__(self, collection=None, parent=None): + super().__init__(parent) + self.collection = collection + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("Coleção") + layout = QVBoxLayout(self) + + # Name + name_layout = QHBoxLayout() + name_layout.addWidget(QLabel("Nome:")) + self.name_edit = QLineEdit() + if self.collection: + self.name_edit.setText(self.collection.name) + name_layout.addWidget(self.name_edit) + layout.addLayout(name_layout) + + # Description + layout.addWidget(QLabel("Descrição:")) + self.desc_edit = QTextEdit() + if self.collection: + self.desc_edit.setText(self.collection.description) + layout.addWidget(self.desc_edit) + + # Color + color_layout = QHBoxLayout() + color_layout.addWidget(QLabel("Cor:")) + self.color_combo = QComboBox() + colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF"] + for color in colors: + self.color_combo.addItem("") + self.color_combo.setItemData( + self.color_combo.count() - 1, + QColor(color), + Qt.BackgroundRole + ) + if self.collection: + index = colors.index(self.collection.color) + self.color_combo.setCurrentIndex(index) + color_layout.addWidget(self.color_combo) + layout.addLayout(color_layout) + + # Buttons + button_layout = QHBoxLayout() + + cancel_btn = QPushButton("Cancelar") + cancel_btn.clicked.connect(self.reject) + button_layout.addWidget(cancel_btn) + + save_btn = QPushButton("Salvar") + save_btn.clicked.connect(self.accept) + button_layout.addWidget(save_btn) + + layout.addLayout(button_layout) + + def get_data(self): + return { + 'name': self.name_edit.text(), + 'description': self.desc_edit.toPlainText(), + 'color': self.color_combo.itemData( + self.color_combo.currentIndex(), + Qt.BackgroundRole + ).name() + } + +class CollectionsWidget(QWidget): + collection_selected = Signal(int) # collection_id + + def __init__(self, parent=None): + super().__init__(parent) + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Toolbar + toolbar = QHBoxLayout() + + new_btn = QPushButton("Nova Coleção") + new_btn.clicked.connect(self.add_collection) + toolbar.addWidget(new_btn) + + toolbar.addStretch() + + layout.addLayout(toolbar) + + # Collections list + self.collections_list = QListWidget() + self.collections_list.itemClicked.connect(self.on_collection_clicked) + self.collections_list.setContextMenuPolicy(Qt.CustomContextMenu) + self.collections_list.customContextMenuRequested.connect( + self.show_context_menu) + layout.addWidget(self.collections_list) + + self.refresh_collections() + + def refresh_collections(self): + self.collections_list.clear() + + try: + session = get_session() + collections = session.query(Collection).all() + + for collection in collections: + item = QListWidgetItem(collection.name) + item.setData(Qt.UserRole, collection.id) + item.setBackground(QColor(collection.color)) + + # Add document count + doc_count = len(collection.documents) + item.setToolTip( + f"{collection.description}\n\n" + f"Documentos: {doc_count}" + ) + + self.collections_list.addItem(item) + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao carregar coleções: {str(e)}") + + def add_collection(self): + dialog = CollectionDialog(parent=self) + if dialog.exec_(): + try: + session = get_session() + + data = dialog.get_data() + collection = Collection( + name=data['name'], + description=data['description'], + color=data['color'] + ) + session.add(collection) + session.commit() + + self.refresh_collections() + + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao criar coleção: {str(e)}") + finally: + session.close() + + def edit_collection(self, collection_id): + try: + session = get_session() + collection = session.query(Collection).get(collection_id) + + if collection: + dialog = CollectionDialog(collection, self) + if dialog.exec_(): + data = dialog.get_data() + collection.name = data['name'] + collection.description = data['description'] + collection.color = data['color'] + session.commit() + + self.refresh_collections() + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao editar coleção: {str(e)}") + + def delete_collection(self, collection_id): + reply = QMessageBox.question( + self, + "Confirmar Exclusão", + "Tem certeza que deseja excluir esta coleção?", + QMessageBox.Yes | QMessageBox.No + ) + + if reply == QMessageBox.Yes: + try: + session = get_session() + collection = session.query(Collection).get(collection_id) + + if collection: + session.delete(collection) + session.commit() + + self.refresh_collections() + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao excluir coleção: {str(e)}") + + def on_collection_clicked(self, item): + collection_id = item.data(Qt.UserRole) + self.collection_selected.emit(collection_id) + + def show_context_menu(self, position): + item = self.collections_list.itemAt(position) + if not item: + return + + collection_id = item.data(Qt.UserRole) + + menu = QMenu() + edit_action = menu.addAction("Editar") + edit_action.triggered.connect( + lambda: self.edit_collection(collection_id)) + + delete_action = menu.addAction("Excluir") + delete_action.triggered.connect( + lambda: self.delete_collection(collection_id)) + + menu.exec_(self.collections_list.mapToGlobal(position)) + +class CollectionDocuments(QWidget): + document_selected = Signal(int) # document_id + + def __init__(self, parent=None): + super().__init__(parent) + self.current_collection = None + self.setup_ui() + + def setup_ui(self): + layout = QVBoxLayout(self) + + # Header + header_layout = QHBoxLayout() + + self.collection_label = QLabel() + header_layout.addWidget(self.collection_label) + + self.add_doc_btn = QPushButton("Adicionar Documento") + self.add_doc_btn.clicked.connect(self.add_document) + header_layout.addWidget(self.add_doc_btn) + + layout.addLayout(header_layout) + + # Documents list + self.documents_list = QListWidget() + self.documents_list.itemClicked.connect(self.on_document_clicked) + self.documents_list.setContextMenuPolicy(Qt.CustomContextMenu) + self.documents_list.customContextMenuRequested.connect( + self.show_context_menu) + layout.addWidget(self.documents_list) + + def load_collection(self, collection_id): + self.current_collection = collection_id + self.refresh_documents() + + def refresh_documents(self): + self.documents_list.clear() + + if not self.current_collection: + return + + try: + session = get_session() + collection = session.query(Collection).get(self.current_collection) + + if collection: + self.collection_label.setText( + f"Coleção: {collection.name}" + ) + + for doc in collection.documents: + item = QListWidgetItem(doc.file_name) + item.setData(Qt.UserRole, doc.id) + item.setToolTip( + f"Marca: {doc.marca}\n" + f"Modelo: {doc.modelo}\n" + f"Ano: {doc.ano}" + ) + self.documents_list.addItem(item) + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao carregar documentos: {str(e)}") + + def add_document(self): + if not self.current_collection: + return + + try: + session = get_session() + + # Get documents not in collection + collection = session.query(Collection).get(self.current_collection) + all_docs = session.query(Document).all() + collection_doc_ids = [doc.id for doc in collection.documents] + available_docs = [doc for doc in all_docs + if doc.id not in collection_doc_ids] + + if not available_docs: + QMessageBox.information( + self, + "Aviso", + "Não há documentos disponíveis para adicionar." + ) + return + + # Show document selection dialog + doc_names = [f"{doc.file_name} ({doc.marca} {doc.modelo})" + for doc in available_docs] + name, ok = QInputDialog.getItem( + self, + "Adicionar Documento", + "Selecione um documento:", + doc_names, + 0, + False + ) + + if ok and name: + index = doc_names.index(name) + doc = available_docs[index] + collection.documents.append(doc) + session.commit() + + self.refresh_documents() + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao adicionar documento: {str(e)}") + + def remove_document(self, doc_id): + if not self.current_collection: + return + + try: + session = get_session() + collection = session.query(Collection).get(self.current_collection) + document = session.query(Document).get(doc_id) + + if collection and document: + collection.documents.remove(document) + session.commit() + + self.refresh_documents() + + session.close() + except Exception as e: + QMessageBox.critical(self, "Erro", + f"Erro ao remover documento: {str(e)}") + + def on_document_clicked(self, item): + doc_id = item.data(Qt.UserRole) + self.document_selected.emit(doc_id) + + def show_context_menu(self, position): + item = self.documents_list.itemAt(position) + if not item: + return + + doc_id = item.data(Qt.UserRole) + + menu = QMenu() + remove_action = menu.addAction("Remover da Coleção") + remove_action.triggered.connect( + lambda: self.remove_document(doc_id)) + + menu.exec_(self.documents_list.mapToGlobal(position))