2026-08-23 14:49:58 +02:00
#include "rulesmanagerdialog.h"
#include "ruleditordialog.h"
#include "db/dao/ruledao.h"
#include "db/dao/categorydao.h"
#include "db/dao/folderdao.h"
#include "services/rulesengine.h"
#include "services/accountservice.h"
#include <QHeaderView>
#include <QMessageBox>
#include <QInputDialog>
#include <QFileDialog>
#include <QJsonDocument>
#include <QJsonObject>
#include <QLabel>
#include <QMenu>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QDebug>
RulesManagerDialog :: RulesManagerDialog ( qint64 accountId , QWidget * parent )
: QDialog ( parent ), m_accountId ( accountId )
{
setWindowTitle ( m_accountId >= 0 ? tr ( "Reglas de la cuenta" ) : tr ( "Reglas globales" ));
resize ( 900 , 600 );
setupUI ();
loadRules ();
}
void RulesManagerDialog :: setupUI ()
{
QVBoxLayout * mainLayout = new QVBoxLayout ( this );
// Toolbar
QHBoxLayout * toolLayout = new QHBoxLayout ();
m_btnNew = new QPushButton ( tr ( "Nueva regla" ));
m_btnRun = new QPushButton ( tr ( "Ejecutar ahora" ));
m_btnImport = new QPushButton ( tr ( "Importar" ));
m_btnExport = new QPushButton ( tr ( "Exportar" ));
m_btnClose = new QPushButton ( tr ( "Cerrar" ));
m_btnNew -> setStyleSheet ( "QPushButton { background: #1976D2; color: white; font-weight: bold; padding: 6px 12px; border-radius: 4px; } QPushButton:hover { background: #1565C0; }" );
m_btnRun -> setStyleSheet ( "QPushButton { background: #4CAF50; color: white; padding: 6px 12px; border-radius: 4px; } QPushButton:hover { background: #43A047; }" );
toolLayout -> addWidget ( m_btnNew );
toolLayout -> addWidget ( m_btnRun );
toolLayout -> addStretch ();
toolLayout -> addWidget ( m_btnImport );
toolLayout -> addWidget ( m_btnExport );
toolLayout -> addWidget ( m_btnClose );
mainLayout -> addLayout ( toolLayout );
// Tree widget
m_tree = new QTreeWidget ();
m_tree -> setHeaderLabels ({ tr ( "Activa" ), tr ( "Nombre" ), tr ( "Prioridad" ), tr ( "Condiciones" ), tr ( "Acciones" ), tr ( "Cuenta" ), tr ( "Última ejecución" ), tr ( "Veces" )});
m_tree -> setAlternatingRowColors ( true );
m_tree -> setSelectionBehavior ( QAbstractItemView :: SelectRows );
m_tree -> setSelectionMode ( QAbstractItemView :: SingleSelection );
m_tree -> setContextMenuPolicy ( Qt :: CustomContextMenu );
m_tree -> header () -> setStretchLastSection ( false );
m_tree -> header () -> setSectionResizeMode ( 1 , QHeaderView :: Stretch );
m_tree -> setColumnWidth ( 0 , 60 );
m_tree -> setColumnWidth ( 2 , 80 );
m_tree -> setColumnWidth ( 3 , 200 );
m_tree -> setColumnWidth ( 4 , 200 );
m_tree -> setColumnWidth ( 5 , 100 );
m_tree -> setColumnWidth ( 6 , 140 );
m_tree -> setColumnWidth ( 7 , 60 );
connect ( m_tree , & QTreeWidget :: customContextMenuRequested , this , [ this ]( const QPoint & pos ) {
QTreeWidgetItem * item = m_tree -> itemAt ( pos );
if ( ! item ) return ;
QMenu menu ( this );
QAction * editAct = menu . addAction ( tr ( "Editar" ), [ this , item ]() { onEditRule ( item ); });
QAction * delAct = menu . addAction ( tr ( "Eliminar" ), [ this , item ]() { onDeleteRule ( item ); });
menu . addSeparator ();
QAction * toggleAct = menu . addAction ( item -> checkState ( 0 ) == Qt :: Checked ? tr ( "Desactivar" ) : tr ( "Activar" ), [ this , item ]() { onToggleEnabled ( item ); });
menu . addSeparator ();
QAction * moveUpAct = menu . addAction ( tr ( "Subir prioridad" ), [ this , item ]() {
int idx = m_tree -> indexOfTopLevelItem ( item );
if ( idx > 0 ) {
QTreeWidgetItem * prev = m_tree -> takeTopLevelItem ( idx );
m_tree -> insertTopLevelItem ( idx - 1 , prev );
// TODO: update priority in DB
}
});
QAction * moveDownAct = menu . addAction ( tr ( "Bajar prioridad" ), [ this , item ]() {
int idx = m_tree -> indexOfTopLevelItem ( item );
if ( idx < m_tree -> topLevelItemCount () - 1 ) {
QTreeWidgetItem * next = m_tree -> takeTopLevelItem ( idx + 1 );
m_tree -> insertTopLevelItem ( idx + 1 , item );
// TODO: update priority in DB
}
});
menu . exec ( m_tree -> viewport () -> mapToGlobal ( pos ));
});
connect ( m_tree , & QTreeWidget :: itemDoubleClicked , this , [ this ]( QTreeWidgetItem * item ) { onEditRule ( item ); });
mainLayout -> addWidget ( m_tree , 1 );
// Status
QLabel * statusLabel = new QLabel ( tr ( "Doble click para editar. Click derecho para más opciones." ));
statusLabel -> setStyleSheet ( "color: #666; font-size: 11px; padding: 4px;" );
mainLayout -> addWidget ( statusLabel );
// Connect buttons
connect ( m_btnNew , & QPushButton :: clicked , this , & RulesManagerDialog :: onNewRule );
connect ( m_btnRun , & QPushButton :: clicked , this , & RulesManagerDialog :: onRunRules );
connect ( m_btnImport , & QPushButton :: clicked , this , [ this ]() { onImportExport ( true ); });
connect ( m_btnExport , & QPushButton :: clicked , this , [ this ]() { onImportExport ( false ); });
connect ( m_btnClose , & QPushButton :: clicked , this , & QDialog :: accept );
}
void RulesManagerDialog :: loadRules ()
{
m_tree -> clear ();
QVector < Rule > rules = RuleDao :: findByAccount ( m_accountId );
for ( const Rule & rule : rules ) {
addRuleToTree ( rule );
}
}
void RulesManagerDialog :: addRuleToTree ( const Rule & rule )
{
QTreeWidgetItem * item = new QTreeWidgetItem ( m_tree );
item -> setData ( 0 , Qt :: UserRole , rule . id );
item -> setCheckState ( 0 , rule . enabled ? Qt :: Checked : Qt :: Unchecked );
item -> setText ( 1 , rule . name );
item -> setText ( 2 , QString :: number ( rule . priority ));
// Conditions summary
QStringList condTexts ;
for ( const RuleCondition & cond : rule . conditions ) {
QString fieldName ;
switch ( cond . field ) {
case RuleCondition :: From : fieldName = "De" ; break ;
case RuleCondition :: To : fieldName = "Para" ; break ;
case RuleCondition :: Cc : fieldName = "CC" ; break ;
case RuleCondition :: Subject : fieldName = "Asunto" ; break ;
case RuleCondition :: Body : fieldName = "Cuerpo" ; break ;
case RuleCondition :: HasAttachment : fieldName = "Tiene adjuntos" ; break ;
case RuleCondition :: Size : fieldName = "Tamaño" ; break ;
case RuleCondition :: Date : fieldName = "Fecha" ; break ;
case RuleCondition :: Flagged : fieldName = "Marcado" ; break ;
case RuleCondition :: Read : fieldName = "Leído" ; break ;
}
QString opName ;
switch ( cond . op ) {
case RuleCondition :: Contains : opName = "contiene" ; break ;
case RuleCondition :: NotContains : opName = "no contiene" ; break ;
case RuleCondition :: Equals : opName = "es" ; break ;
case RuleCondition :: NotEquals : opName = "no es" ; break ;
case RuleCondition :: StartsWith : opName = "empieza por" ; break ;
case RuleCondition :: EndsWith : opName = "termina en" ; break ;
case RuleCondition :: Regex : opName = "regex" ; break ;
case RuleCondition :: GreaterThan : opName = ">" ; break ;
case RuleCondition :: LessThan : opName = "<" ; break ;
case RuleCondition :: Before : opName = "antes de" ; break ;
case RuleCondition :: After : opName = "después de" ; break ;
}
condTexts . append ( QString ( "%1 %2 \" %3 \" " ). arg ( fieldName , opName , cond . value ));
}
item -> setText ( 3 , condTexts . join ( rule . matchAll ? " Y " : " O " ));
// Actions summary
QStringList actTexts ;
for ( const RuleAction & act : rule . actions ) {
QString actName ;
switch ( act . type ) {
case RuleAction :: MoveToFolder : actName = QString ( "Mover a carpeta %1" ). arg ( act . value ); break ;
case RuleAction :: MarkAsRead : actName = QString ( "Marcar como %1" ). arg ( act . value == "false" ? "no leído" : "leído" ); break ;
case RuleAction :: MarkAsFlagged : actName = QString ( "Marcar como %1" ). arg ( act . value == "false" ? "no marcado" : "marcado" ); break ;
case RuleAction :: Delete : actName = "Eliminar" ; break ;
case RuleAction :: AssignCategory : actName = QString ( "Categoría %1" ). arg ( act . value ); break ;
case RuleAction :: RemoveCategory : actName = QString ( "Quitar categoría %1" ). arg ( act . value ); break ;
case RuleAction :: ForwardTo : actName = QString ( "Reenviar a %1" ). arg ( act . value ); break ;
case RuleAction :: SetPriority : actName = QString ( "Prioridad %1" ). arg ( act . value ); break ;
case RuleAction :: StopProcessing : actName = "Detener procesamiento" ; break ;
}
actTexts . append ( actName );
}
item -> setText ( 4 , actTexts . join ( ", " ));
item -> setText ( 5 , rule . isGlobal () ? tr ( "Global" ) : QString :: number ( rule . accountId ));
item -> setText ( 6 , rule . lastRun . isValid () ? rule . lastRun . toString ( "dd/MM/yyyy hh:mm" ) : tr ( "Nunca" ));
item -> setText ( 7 , QString :: number ( rule . runCount ));
if ( ! rule . enabled ) {
for ( int i = 0 ; i < m_tree -> columnCount (); ++ i ) {
item -> setForeground ( i , QBrush ( QColor ( "#999" )));
}
}
}
Rule RulesManagerDialog :: itemToRule ( QTreeWidgetItem * item ) const
{
qint64 id = item -> data ( 0 , Qt :: UserRole ). toLongLong ();
auto ruleOpt = RuleDao :: findById ( id );
return ruleOpt . value_or ( Rule ());
}
void RulesManagerDialog :: onNewRule ()
{
RuleEditorDialog dlg ( Rule (), m_accountId , this );
connect ( & dlg , & RuleEditorDialog :: ruleSaved , this , & RulesManagerDialog :: onRuleSaved );
dlg . exec ();
}
void RulesManagerDialog :: onEditRule ( QTreeWidgetItem * item )
{
Rule rule = itemToRule ( item );
if ( ! rule . isValid ()) return ;
RuleEditorDialog dlg ( rule , m_accountId , this );
connect ( & dlg , & RuleEditorDialog :: ruleSaved , this , & RulesManagerDialog :: onRuleSaved );
dlg . exec ();
}
void RulesManagerDialog :: onRuleSaved ( const Rule & rule )
{
refresh ();
emit rulesChanged ();
}
void RulesManagerDialog :: onDeleteRule ( QTreeWidgetItem * item )
{
Rule rule = itemToRule ( item );
if ( ! rule . isValid ()) return ;
if ( QMessageBox :: question ( this , tr ( "Eliminar regla" ),
tr ( "¿Eliminar la regla \" %1 \" ?" ). arg ( rule . name ),
QMessageBox :: Yes | QMessageBox :: No ) == QMessageBox :: Yes ) {
if ( RuleDao :: remove ( rule . id )) {
refresh ();
emit rulesChanged ();
}
}
}
void RulesManagerDialog :: onToggleEnabled ( QTreeWidgetItem * item )
{
Rule rule = itemToRule ( item );
if ( ! rule . isValid ()) return ;
bool newState = item -> checkState ( 0 ) != Qt :: Checked ;
if ( RuleDao :: setEnabled ( rule . id , newState )) {
item -> setCheckState ( 0 , newState ? Qt :: Checked : Qt :: Unchecked );
if ( ! newState ) {
for ( int i = 0 ; i < m_tree -> columnCount (); ++ i ) {
item -> setForeground ( i , QBrush ( QColor ( "#999" )));
}
} else {
for ( int i = 0 ; i < m_tree -> columnCount (); ++ i ) {
item -> setForeground ( i , QBrush ( QColor ( "#000" )));
}
}
emit rulesChanged ();
}
}
void RulesManagerDialog :: onRunRules ()
{
// TODO: Get selected folder/account from main window
QMessageBox :: information ( this , tr ( "Ejecutar reglas" ), tr ( "Seleccione una carpeta en la ventana principal y pulse 'Ejecutar reglas' en el menú de herramientas." ));
}
void RulesManagerDialog :: onImportExport ( bool import )
{
if ( import ) {
QString file = QFileDialog :: getOpenFileName ( this , tr ( "Importar reglas" ), QString (), tr ( "JSON (*.json)" ));
if ( file . isEmpty ()) return ;
// TODO: Parse JSON and create rules
QMessageBox :: information ( this , tr ( "Importar" ), tr ( "Función de importación en desarrollo." ));
} else {
QString file = QFileDialog :: getSaveFileName ( this , tr ( "Exportar reglas" ), "rules.json" , tr ( "JSON (*.json)" ));
if ( file . isEmpty ()) return ;
QVector < Rule > rules = RuleDao :: findByAccount ( m_accountId );
QJsonArray arr ;
for ( const Rule & rule : rules ) arr . append ( rule . toJson ());
QJsonDocument doc ( arr );
QFile f ( file );
if ( f . open ( QIODevice :: WriteOnly )) {
f . write ( doc . toJson ( QJsonDocument :: Indented ));
QMessageBox :: information ( this , tr ( "Exportar" ), tr ( "Reglas exportadas a %1" ). arg ( file ));
}
}
}
void RulesManagerDialog :: refresh ()
{
loadRules ();
2026-08-28 13:45:21 +02:00
}