]> Nutra Git (v1) - nutratech/gui.git/commitdiff
add recipes; format
authorShane Jaroch <chown_tee@proton.me>
Wed, 21 Jan 2026 18:56:03 +0000 (13:56 -0500)
committerShane Jaroch <chown_tee@proton.me>
Wed, 21 Jan 2026 18:56:03 +0000 (13:56 -0500)
23 files changed:
.clang-format [new file with mode: 0644]
include/db/databasemanager.h
include/db/foodrepository.h
include/db/mealrepository.h [new file with mode: 0644]
include/mainwindow.h
include/utils/string_utils.h
include/widgets/detailswidget.h
include/widgets/mealwidget.h
include/widgets/rdasettingswidget.h
include/widgets/searchwidget.h
include/widgets/weightinputdialog.h
src/db/databasemanager.cpp
src/db/foodrepository.cpp
src/db/mealrepository.cpp [new file with mode: 0644]
src/main.cpp
src/mainwindow.cpp
src/utils/string_utils.cpp
src/widgets/detailswidget.cpp
src/widgets/mealwidget.cpp
src/widgets/rdasettingswidget.cpp
src/widgets/searchwidget.cpp
src/widgets/weightinputdialog.cpp
tests/test_foodrepository.cpp

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..3f1dc11
--- /dev/null
@@ -0,0 +1,7 @@
+BasedOnStyle: Google
+IndentWidth: 4
+TabWidth: 4
+AccessModifierOffset: -4
+ColumnLimit: 100
+AllowShortFunctionsOnASingleLine: Empty
+KeepEmptyLinesAtTheStartOfBlocks: false
index f6597e60a636c23a16dd6fa574f648138d5d5680..17336fcae048e1db5c061400b614b988f9e28ce7 100644 (file)
@@ -7,23 +7,23 @@
 
 class DatabaseManager {
 public:
-  static DatabaseManager &instance();
-  bool connect(const QString &path);
-  [[nodiscard]] bool isOpen() const;
-  [[nodiscard]] QSqlDatabase database() const;
-  [[nodiscard]] QSqlDatabase userDatabase() const;
+    static DatabaseManager& instance();
+    bool connect(const QString& path);
+    [[nodiscard]] bool isOpen() const;
+    [[nodiscard]] QSqlDatabase database() const;
+    [[nodiscard]] QSqlDatabase userDatabase() const;
 
-  DatabaseManager(const DatabaseManager &) = delete;
-  DatabaseManager &operator=(const DatabaseManager &) = delete;
+    DatabaseManager(const DatabaseManager&) = delete;
+    DatabaseManager& operator=(const DatabaseManager&) = delete;
 
 private:
-  DatabaseManager();
-  ~DatabaseManager();
+    DatabaseManager();
+    ~DatabaseManager();
 
-  void initUserDatabase();
+    void initUserDatabase();
 
-  QSqlDatabase m_db;
-  QSqlDatabase m_userDb;
+    QSqlDatabase m_db;
+    QSqlDatabase m_userDb;
 };
 
-#endif // DATABASEMANAGER_H
+#endif  // DATABASEMANAGER_H
index e2503e4ab4383ec2d3d059b31c315acc7e8ff0aa..cb2d2660be2146663f2aedb6ff51fb5558a5fe85 100644 (file)
@@ -6,59 +6,59 @@
 #include <vector>
 
 struct Nutrient {
-  int id;
-  QString description;
-  double amount;
-  QString unit;
-  double rdaPercentage; // Calculated
+    int id;
+    QString description;
+    double amount;
+    QString unit;
+    double rdaPercentage;  // Calculated
 };
 
 struct ServingWeight {
-  QString description;
-  double grams;
+    QString description;
+    double grams;
 };
 
 struct FoodItem {
-  int id;
-  QString description;
-  QString foodGroupName;
-  int nutrientCount;
-  int aminoCount;
-  int flavCount;
-  int score;                       // For search results
-  std::vector<Nutrient> nutrients; // Full details for results
+    int id;
+    QString description;
+    QString foodGroupName;
+    int nutrientCount;
+    int aminoCount;
+    int flavCount;
+    int score;                        // For search results
+    std::vector<Nutrient> nutrients;  // Full details for results
 };
 
 class FoodRepository {
 public:
-  explicit FoodRepository();
+    explicit FoodRepository();
 
-  // Search foods by keyword
-  std::vector<FoodItem> searchFoods(const QString &query);
+    // Search foods by keyword
+    std::vector<FoodItem> searchFoods(const QString& query);
 
-  // Get detailed nutrients for a generic food (100g)
-  // Returns a list of nutrients
-  std::vector<Nutrient> getFoodNutrients(int foodId);
+    // Get detailed nutrients for a generic food (100g)
+    // Returns a list of nutrients
+    std::vector<Nutrient> getFoodNutrients(int foodId);
 
-  // Get available serving weights (units) for a food
-  std::vector<ServingWeight> getFoodServings(int foodId);
+    // Get available serving weights (units) for a food
+    std::vector<ServingWeight> getFoodServings(int foodId);
 
-  // RDA methods
-  std::map<int, double> getNutrientRdas();
-  void updateRda(int nutrId, double value);
+    // RDA methods
+    std::map<int, double> getNutrientRdas();
+    void updateRda(int nutrId, double value);
 
-  // Helper to get nutrient definition basics if needed
-  // QString getNutrientName(int nutrientId);
+    // Helper to get nutrient definition basics if needed
+    // QString getNutrientName(int nutrientId);
 
 private:
-  // Internal helper methods
-  void ensureCacheLoaded();
-  void loadRdas();
+    // Internal helper methods
+    void ensureCacheLoaded();
+    void loadRdas();
 
-  bool m_cacheLoaded = false;
-  // Cache stores basic food info
-  std::vector<FoodItem> m_cache;
-  std::map<int, double> m_rdas;
+    bool m_cacheLoaded = false;
+    // Cache stores basic food info
+    std::vector<FoodItem> m_cache;
+    std::map<int, double> m_rdas;
 };
 
-#endif // FOODREPOSITORY_H
+#endif  // FOODREPOSITORY_H
diff --git a/include/db/mealrepository.h b/include/db/mealrepository.h
new file mode 100644 (file)
index 0000000..00ca2bf
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef MEALREPOSITORY_H
+#define MEALREPOSITORY_H
+
+#include <QDate>
+#include <QString>
+#include <map>
+#include <vector>
+
+struct MealLogItem {
+    int id;  // log_food.id
+    int foodId;
+    double grams;
+    int mealId;
+    QString mealName;
+    // Potentially cached description?
+    QString foodName;  // Joined from food_des if we query it
+};
+
+class MealRepository {
+public:
+    MealRepository();
+
+    // Meal Names (Breakfast, Lunch, etc.)
+    std::map<int, QString> getMealNames();
+
+    // Logging
+    void addFoodLog(int foodId, double grams, int mealId, QDate date = QDate::currentDate());
+    std::vector<MealLogItem> getDailyLogs(QDate date = QDate::currentDate());
+    void clearDailyLogs(QDate date = QDate::currentDate());
+    void removeLogEntry(int logId);
+
+private:
+    std::map<int, QString> m_mealNamesCache;
+    void ensureMealNamesLoaded();
+};
+
+#endif  // MEALREPOSITORY_H
index 1a8c277b2bdfc69fb58c20ef969010425080836e..07a1caa21d7bd8e91345a7b684f205ca205bd9ca 100644 (file)
@@ -1,39 +1,40 @@
 #ifndef MAINWINDOW_H
 #define MAINWINDOW_H
 
+#include <QMainWindow>
+#include <QTabWidget>
+
 #include "widgets/detailswidget.h"
 #include "widgets/mealwidget.h"
 #include "widgets/searchwidget.h"
-#include <QMainWindow>
-#include <QTabWidget>
 
 class MainWindow : public QMainWindow {
-  Q_OBJECT
+    Q_OBJECT
 
 public:
-  MainWindow(QWidget *parent = nullptr);
-  ~MainWindow() override;
+    MainWindow(QWidget* parent = nullptr);
+    ~MainWindow() override;
 
 private slots:
-  void onOpenDatabase();
-  void onRecentFileClick();
-  void onSettings();
-  void onAbout();
+    void onOpenDatabase();
+    void onRecentFileClick();
+    void onSettings();
+    void onAbout();
 
 private:
-  void setupUi();
-  void updateRecentFileActions();
-  void addToRecentFiles(const QString &path);
-
-  QTabWidget *tabs;
-  SearchWidget *searchWidget;
-  DetailsWidget *detailsWidget;
-  MealWidget *mealWidget;
-  FoodRepository repository;
-
-  QMenu *recentFilesMenu;
-  static constexpr int MaxRecentFiles = 5;
-  QAction *recentFileActions[MaxRecentFiles];
+    void setupUi();
+    void updateRecentFileActions();
+    void addToRecentFiles(const QString& path);
+
+    QTabWidget* tabs;
+    SearchWidget* searchWidget;
+    DetailsWidget* detailsWidget;
+    MealWidget* mealWidget;
+    FoodRepository repository;
+
+    QMenu* recentFilesMenu;
+    static constexpr int MaxRecentFiles = 5;
+    QAction* recentFileActions[MaxRecentFiles];
 };
 
-#endif // MAINWINDOW_H
+#endif  // MAINWINDOW_H
index 58c07b1570c4047f75e5064fc26ee3fac65ead69..1c52fdd1a14ec4e5a15935a0b52e636fd61d90c0 100644 (file)
@@ -8,12 +8,12 @@
 namespace Utils {
 
 // Calculate Levenshtein distance between two strings
-int levenshteinDistance(const QString &s1, const QString &s2);
+int levenshteinDistance(const QString& s1, const QString& s2);
 
 // Calculate a simple fuzzy match score (0-100)
 // Higher is better.
-int calculateFuzzyScore(const QString &query, const QString &target);
+int calculateFuzzyScore(const QString& query, const QString& target);
 
-} // namespace Utils
+}  // namespace Utils
 
-#endif // STRING_UTILS_H
+#endif  // STRING_UTILS_H
index 51b74e64a9c702c3d939891e3cc40be60a1cacc2..8bb23d55bf438246203a21d25eec59f4912a63c7 100644 (file)
@@ -1,34 +1,35 @@
 #ifndef DETAILSWIDGET_H
 #define DETAILSWIDGET_H
 
-#include "db/foodrepository.h"
 #include <QLabel>
 #include <QPushButton>
 #include <QTableWidget>
 #include <QWidget>
 
+#include "db/foodrepository.h"
+
 class DetailsWidget : public QWidget {
-  Q_OBJECT
+    Q_OBJECT
 
 public:
-  explicit DetailsWidget(QWidget *parent = nullptr);
+    explicit DetailsWidget(QWidget* parent = nullptr);
 
-  void loadFood(int foodId, const QString &foodName);
+    void loadFood(int foodId, const QString& foodName);
 
 signals:
-  void addToMeal(int foodId, const QString &foodName, double grams);
+    void addToMeal(int foodId, const QString& foodName, double grams);
 
 private slots:
-  void onAddClicked();
+    void onAddClicked();
 
 private:
-  QLabel *nameLabel;
-  QTableWidget *nutrientsTable;
-  QPushButton *addButton;
-  FoodRepository repository;
+    QLabel* nameLabel;
+    QTableWidget* nutrientsTable;
+    QPushButton* addButton;
+    FoodRepository repository;
 
-  int currentFoodId;
-  QString currentFoodName;
+    int currentFoodId;
+    QString currentFoodName;
 };
 
-#endif // DETAILSWIDGET_H
+#endif  // DETAILSWIDGET_H
index cbf163b3a7138e5bf17ea3eba6b9cb0b0649aa7b..041a4ba4251443422929908f37a020fd2c3d8d1b 100644 (file)
@@ -1,40 +1,44 @@
 #ifndef MEALWIDGET_H
 #define MEALWIDGET_H
 
-#include "db/foodrepository.h"
 #include <QPushButton>
 #include <QTableWidget>
 #include <QWidget>
-#include <map>
 #include <vector>
 
+#include "db/foodrepository.h"
+#include "db/mealrepository.h"
+
 struct MealItem {
-  int foodId;
-  QString name;
-  double grams;
-  std::vector<Nutrient> nutrients_100g; // Base nutrients
+    int foodId;
+    QString name;
+    double grams;
+    std::vector<Nutrient> nutrients_100g;
 };
 
 class MealWidget : public QWidget {
-  Q_OBJECT
+    Q_OBJECT
 
 public:
-  explicit MealWidget(QWidget *parent = nullptr);
+    explicit MealWidget(QWidget* parent = nullptr);
 
-  void addFood(int foodId, const QString &foodName, double grams);
+    void addFood(int foodId, const QString& foodName, double grams);
 
 private slots:
-  void clearMeal();
+    void clearMeal();
 
 private:
-  void updateTotals();
+    void updateTotals();
+    void refresh();
+
+    QTableWidget* itemsTable;
+    QPushButton* clearButton;
+    QTableWidget* totalsTable;
 
-  QTableWidget *itemsTable;
-  QTableWidget *totalsTable;
-  QPushButton *clearButton;
+    FoodRepository repository;
+    MealRepository m_mealRepo;
 
-  std::vector<MealItem> mealItems;
-  FoodRepository repository;
+    std::vector<MealItem> mealItems;
 };
 
-#endif // MEALWIDGET_H
+#endif  // MEALWIDGET_H
index c8284f0ad2412dc23a1e157a4b9665836d02bb4e..54c17cc31736ae46bf591e2303ba844026e4bc40 100644 (file)
@@ -1,26 +1,26 @@
 #ifndef RDASETTINGSWIDGET_H
 #define RDASETTINGSWIDGET_H
 
-#include "db/foodrepository.h"
 #include <QDialog>
 #include <QTableWidget>
 
+#include "db/foodrepository.h"
+
 class RDASettingsWidget : public QDialog {
-  Q_OBJECT
+    Q_OBJECT
 
 public:
-  explicit RDASettingsWidget(FoodRepository &repository,
-                             QWidget *parent = nullptr);
+    explicit RDASettingsWidget(FoodRepository& repository, QWidget* parent = nullptr);
 
 private slots:
-  void onCellChanged(int row, int column);
+    void onCellChanged(int row, int column);
 
 private:
-  void loadData();
+    void loadData();
 
-  FoodRepository &m_repository;
-  QTableWidget *m_table;
-  bool m_loading = false;
+    FoodRepository& m_repository;
+    QTableWidget* m_table;
+    bool m_loading = false;
 };
 
-#endif // RDASETTINGSWIDGET_H
+#endif  // RDASETTINGSWIDGET_H
index 03d929e35623b7fabadb493dd8f20fd243fa8587..9b109aa3565bec6bbb083eb8eca66d20e26a801d 100644 (file)
@@ -1,34 +1,35 @@
 #ifndef SEARCHWIDGET_H
 #define SEARCHWIDGET_H
 
-#include "db/foodrepository.h"
 #include <QLineEdit>
 #include <QPushButton>
 #include <QTableWidget>
 #include <QTimer>
 #include <QWidget>
 
+#include "db/foodrepository.h"
+
 class SearchWidget : public QWidget {
-  Q_OBJECT
+    Q_OBJECT
 
 public:
-  explicit SearchWidget(QWidget *parent = nullptr);
+    explicit SearchWidget(QWidget* parent = nullptr);
 
 signals:
-  void foodSelected(int foodId, const QString &foodName);
-  void addToMealRequested(int foodId, const QString &foodName, double grams);
+    void foodSelected(int foodId, const QString& foodName);
+    void addToMealRequested(int foodId, const QString& foodName, double grams);
 
 private slots:
-  void performSearch();
-  void onRowDoubleClicked(int row, int column);
-  void onCustomContextMenu(const QPoint &pos);
+    void performSearch();
+    void onRowDoubleClicked(int row, int column);
+    void onCustomContextMenu(const QPoint& pos);
 
 private:
-  QLineEdit *searchInput;
-  QPushButton *searchButton;
-  QTableWidget *resultsTable;
-  FoodRepository repository;
-  QTimer *searchTimer;
+    QLineEdit* searchInput;
+    QPushButton* searchButton;
+    QTableWidget* resultsTable;
+    FoodRepository repository;
+    QTimer* searchTimer;
 };
 
-#endif // SEARCHWIDGET_H
+#endif  // SEARCHWIDGET_H
index bbc13a4574bf4c3b016c86c7704e90cc6967411b..e9781725e3fecca1fd2e128f18a8ec79f3249db3 100644 (file)
@@ -1,29 +1,29 @@
 #ifndef WEIGHTINPUTDIALOG_H
 #define WEIGHTINPUTDIALOG_H
 
-#include "db/foodrepository.h"
 #include <QComboBox>
 #include <QDialog>
 #include <QDoubleSpinBox>
 #include <QLabel>
 
+#include "db/foodrepository.h"
+
 class WeightInputDialog : public QDialog {
-  Q_OBJECT
+    Q_OBJECT
 
 public:
-  explicit WeightInputDialog(const QString &foodName,
-                             const std::vector<ServingWeight> &servings,
-                             QWidget *parent = nullptr);
+    explicit WeightInputDialog(const QString& foodName, const std::vector<ServingWeight>& servings,
+                               QWidget* parent = nullptr);
 
-  double getGrams() const;
+    double getGrams() const;
 
 private:
-  QDoubleSpinBox *amountSpinBox;
-  QComboBox *unitComboBox;
-  std::vector<ServingWeight> m_servings;
+    QDoubleSpinBox* amountSpinBox;
+    QComboBox* unitComboBox;
+    std::vector<ServingWeight> m_servings;
 
-  static constexpr double GRAMS_PER_OZ = 28.3495;
-  static constexpr double GRAMS_PER_LB = 453.592;
+    static constexpr double GRAMS_PER_OZ = 28.3495;
+    static constexpr double GRAMS_PER_LB = 453.592;
 };
 
-#endif // WEIGHTINPUTDIALOG_H
+#endif  // WEIGHTINPUTDIALOG_H
index 131b87075759a83677b5f1c116e42ee3ba353cb9..e64b75edebf959449c5f45ad99256e1bd9c64c30 100644 (file)
 #include "db/databasemanager.h"
+
 #include <QDebug>
 #include <QDir>
 #include <QFileInfo>
 #include <QSqlError>
 #include <QVariant>
 
-DatabaseManager &DatabaseManager::instance() {
-  static DatabaseManager instance;
-  return instance;
+DatabaseManagerDatabaseManager::instance() {
+    static DatabaseManager instance;
+    return instance;
 }
 
 DatabaseManager::DatabaseManager() {
-  m_userDb = QSqlDatabase::addDatabase("QSQLITE", "user_db");
-  initUserDatabase();
+    m_userDb = QSqlDatabase::addDatabase("QSQLITE", "user_db");
+    initUserDatabase();
 }
 
 DatabaseManager::~DatabaseManager() {
-  if (m_db.isOpen()) {
-    m_db.close();
-  }
+    if (m_db.isOpen()) {
+        m_db.close();
+    }
 }
 
-bool DatabaseManager::connect(const QString &path) {
-  if (m_db.isOpen()) {
-    return true;
-  }
+bool DatabaseManager::connect(const QStringpath) {
+    if (m_db.isOpen()) {
+        return true;
+    }
 
-  if (!QFileInfo::exists(path)) {
-    qCritical() << "Database file not found:" << path;
-    return false;
-  }
+    if (!QFileInfo::exists(path)) {
+        qCritical() << "Database file not found:" << path;
+        return false;
+    }
 
-  m_db = QSqlDatabase::addDatabase("QSQLITE");
-  m_db.setDatabaseName(path);
-  m_db.setConnectOptions("QSQLITE_OPEN_READONLY");
+    m_db = QSqlDatabase::addDatabase("QSQLITE");
+    m_db.setDatabaseName(path);
+    m_db.setConnectOptions("QSQLITE_OPEN_READONLY");
 
-  if (!m_db.open()) {
-    qCritical() << "Error opening database:" << m_db.lastError().text();
-    return false;
-  }
+    if (!m_db.open()) {
+        qCritical() << "Error opening database:" << m_db.lastError().text();
+        return false;
+    }
 
-  return true;
+    return true;
 }
 
-bool DatabaseManager::isOpen() const { return m_db.isOpen(); }
+bool DatabaseManager::isOpen() const {
+    return m_db.isOpen();
+}
 
-QSqlDatabase DatabaseManager::database() const { return m_db; }
+QSqlDatabase DatabaseManager::database() const {
+    return m_db;
+}
 
-QSqlDatabase DatabaseManager::userDatabase() const { return m_userDb; }
+QSqlDatabase DatabaseManager::userDatabase() const {
+    return m_userDb;
+}
 
 void DatabaseManager::initUserDatabase() {
-  QString path = QDir::homePath() + "/.nutra/nt.sqlite3";
-  m_userDb.setDatabaseName(path);
-
-  if (!m_userDb.open()) {
-    qCritical() << "Failed to open user database:"
-                << m_userDb.lastError().text();
-    return;
-  }
-
-  QSqlQuery query(m_userDb);
-  // Create profile table (simplified version of CLI's schema)
-  if (!query.exec("CREATE TABLE IF NOT EXISTS profile ("
-                  "id INTEGER PRIMARY KEY AUTOINCREMENT, "
-                  "name TEXT UNIQUE NOT NULL)")) {
-    qCritical() << "Failed to create profile table:"
-                << query.lastError().text();
-  }
-
-  // Ensure default profile exists
-  query.exec("INSERT OR IGNORE INTO profile (id, name) VALUES (1, 'default')");
-
-  // Create rda table
-  if (!query.exec("CREATE TABLE IF NOT EXISTS rda ("
-                  "profile_id INTEGER NOT NULL, "
-                  "nutr_id INTEGER NOT NULL, "
-                  "rda REAL NOT NULL, "
-                  "PRIMARY KEY (profile_id, nutr_id), "
-                  "FOREIGN KEY (profile_id) REFERENCES profile (id))")) {
-    qCritical() << "Failed to create rda table:" << query.lastError().text();
-  }
+    QString path = QDir::homePath() + "/.nutra/nt.sqlite3";
+    m_userDb.setDatabaseName(path);
+
+    if (!m_userDb.open()) {
+        qCritical() << "Failed to open user database:" << m_userDb.lastError().text();
+        return;
+    }
+
+    QSqlQuery query(m_userDb);
+
+    // Helper to execute schema creation
+    auto createTable = [&](const QString& sql) {
+        if (!query.exec(sql)) {
+            qCritical() << "Failed to create table:" << query.lastError().text() << "\nSQL:" << sql;
+        }
+    };
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS version ("
+        "id integer PRIMARY KEY AUTOINCREMENT, "
+        "version text NOT NULL UNIQUE, "
+        "created date NOT NULL, "
+        "notes text)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS bmr_eq ("
+        "id integer PRIMARY KEY, "
+        "name text NOT NULL UNIQUE)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS bf_eq ("
+        "id integer PRIMARY KEY, "
+        "name text NOT NULL UNIQUE)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS profile ("
+        "id integer PRIMARY KEY AUTOINCREMENT, "
+        "uuid int NOT NULL DEFAULT (RANDOM()), "
+        "name text NOT NULL UNIQUE, "
+        "gender text, "
+        "dob date, "
+        "act_lvl int DEFAULT 2, "
+        "goal_wt real, "
+        "goal_bf real DEFAULT 18, "
+        "bmr_eq_id int DEFAULT 1, "
+        "bf_eq_id int DEFAULT 1, "
+        "created int DEFAULT (strftime ('%s', 'now')), "
+        "FOREIGN KEY (bmr_eq_id) REFERENCES bmr_eq (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE, "
+        "FOREIGN KEY (bf_eq_id) REFERENCES bf_eq (id) ON UPDATE CASCADE "
+        "ON DELETE CASCADE)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS rda ("
+        "profile_id int NOT NULL, "
+        "nutr_id int NOT NULL, "
+        "rda real NOT NULL, "
+        "PRIMARY KEY (profile_id, nutr_id), "
+        "FOREIGN KEY (profile_id) REFERENCES profile (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS custom_food ("
+        "id integer PRIMARY KEY AUTOINCREMENT, "
+        "tagname text NOT NULL UNIQUE, "
+        "name text NOT NULL UNIQUE, "
+        "created int DEFAULT (strftime ('%s', 'now')))");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS cf_dat ("
+        "cf_id int NOT NULL, "
+        "nutr_id int NOT NULL, "
+        "nutr_val real NOT NULL, "
+        "notes text, "
+        "created int DEFAULT (strftime ('%s', 'now')), "
+        "PRIMARY KEY (cf_id, nutr_id), "
+        "FOREIGN KEY (cf_id) REFERENCES custom_food (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS meal_name ("
+        "id integer PRIMARY KEY AUTOINCREMENT, "
+        "name text NOT NULL)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS log_food ("
+        "id integer PRIMARY KEY AUTOINCREMENT, "
+        "profile_id int NOT NULL, "
+        "date int DEFAULT (strftime ('%s', 'now')), "
+        "meal_id int NOT NULL, "
+        "food_id int NOT NULL, "
+        "msre_id int NOT NULL, "
+        "amt real NOT NULL, "
+        "created int DEFAULT (strftime ('%s', 'now')), "
+        "FOREIGN KEY (profile_id) REFERENCES profile (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE, "
+        "FOREIGN KEY (meal_id) REFERENCES meal_name (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE)");
+
+    createTable(
+        "CREATE TABLE IF NOT EXISTS log_cf ("
+        "id integer PRIMARY KEY AUTOINCREMENT, "
+        "profile_id int NOT NULL, "
+        "date int DEFAULT (strftime ('%s', 'now')), "
+        "meal_id int NOT NULL, "
+        "food_id int NOT NULL, "
+        "custom_food_id int, "
+        "msre_id int NOT NULL, "
+        "amt real NOT NULL, "
+        "created int DEFAULT (strftime ('%s', 'now')), "
+        "FOREIGN KEY (profile_id) REFERENCES profile (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE, "
+        "FOREIGN KEY (meal_id) REFERENCES meal_name (id) ON UPDATE "
+        "CASCADE ON DELETE CASCADE, "
+        "FOREIGN KEY (custom_food_id) REFERENCES custom_food (id) ON "
+        "UPDATE CASCADE ON DELETE CASCADE)");
+
+    // Default Data Seeding
+
+    // Ensure default profile exists
+    query.exec("INSERT OR IGNORE INTO profile (id, name) VALUES (1, 'default')");
+
+    // Seed standard meal names if table is empty
+    query.exec("SELECT count(*) FROM meal_name");
+    if (query.next() && query.value(0).toInt() == 0) {
+        QStringList meals = {"Breakfast", "Lunch", "Dinner", "Snack", "Brunch"};
+        for (const auto& meal : meals) {
+            query.prepare("INSERT INTO meal_name (name) VALUES (?)");
+            query.addBindValue(meal);
+            query.exec();
+        }
+    }
 }
index a18689539df54c4869568942075904e1303af74b..c8787239a306a9b799f40a59d50fd94c25b4aec4 100644 (file)
 #include "db/foodrepository.h"
-#include "db/databasemanager.h"
+
 #include <QDebug>
 #include <QSqlError>
 #include <QSqlQuery>
 #include <QVariant>
 #include <map>
 
+#include "db/databasemanager.h"
+
 FoodRepository::FoodRepository() {}
 
-#include "utils/string_utils.h"
 #include <algorithm>
 
+#include "utils/string_utils.h"
+
 // ...
 
 void FoodRepository::ensureCacheLoaded() {
-  if (m_cacheLoaded)
-    return;
-
-  QSqlDatabase db = DatabaseManager::instance().database();
-  if (!db.isOpen())
-    return;
-
-  // 1. Load Food Items with Group Names
-  QSqlQuery query("SELECT f.id, f.long_desc, g.fdgrp_desc "
-                  "FROM food_des f "
-                  "JOIN fdgrp g ON f.fdgrp_id = g.id",
-                  db);
-  std::map<int, int> nutrientCounts;
-
-  // 2. Load Nutrient Counts (Bulk)
-  QSqlQuery countQuery(
-      "SELECT food_id, count(*) FROM nut_data GROUP BY food_id", db);
-  while (countQuery.next()) {
-    nutrientCounts[countQuery.value(0).toInt()] = countQuery.value(1).toInt();
-  }
-
-  while (query.next()) {
-    FoodItem item;
-    item.id = query.value(0).toInt();
-    item.description = query.value(1).toString();
-    item.foodGroupName = query.value(2).toString();
-
-    // Set counts from map (default 0 if not found)
-    auto it = nutrientCounts.find(item.id);
-    item.nutrientCount = (it != nutrientCounts.end()) ? it->second : 0;
-
-    item.aminoCount = 0; // TODO: Implement specific counts if needed
-    item.flavCount = 0;
-    item.score = 0;
-    m_cache.push_back(item);
-  }
-  loadRdas();
-  m_cacheLoaded = true;
-}
-
-void FoodRepository::loadRdas() {
-  m_rdas.clear();
-  QSqlDatabase db = DatabaseManager::instance().database();
-  QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+    if (m_cacheLoaded) return;
 
-  // 1. Load Defaults from USDA
-  if (db.isOpen()) {
-    QSqlQuery query("SELECT id, rda FROM nutrients_overview", db);
-    while (query.next()) {
-      m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+    QSqlDatabase db = DatabaseManager::instance().database();
+    if (!db.isOpen()) return;
+
+    // 1. Load Food Items with Group Names
+    QSqlQuery query(
+        "SELECT f.id, f.long_desc, g.fdgrp_desc "
+        "FROM food_des f "
+        "JOIN fdgrp g ON f.fdgrp_id = g.id",
+        db);
+    std::map<int, int> nutrientCounts;
+
+    // 2. Load Nutrient Counts (Bulk)
+    QSqlQuery countQuery("SELECT food_id, count(*) FROM nut_data GROUP BY food_id", db);
+    while (countQuery.next()) {
+        nutrientCounts[countQuery.value(0).toInt()] = countQuery.value(1).toInt();
     }
-  }
 
-  // 2. Load Overrides from User DB
-  if (userDb.isOpen()) {
-    QSqlQuery query("SELECT nutr_id, rda FROM rda WHERE profile_id = 1",
-                    userDb);
     while (query.next()) {
-      m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+        FoodItem item;
+        item.id = query.value(0).toInt();
+        item.description = query.value(1).toString();
+        item.foodGroupName = query.value(2).toString();
+
+        // Set counts from map (default 0 if not found)
+        auto it = nutrientCounts.find(item.id);
+        item.nutrientCount = (it != nutrientCounts.end()) ? it->second : 0;
+
+        item.aminoCount = 0;  // TODO: Implement specific counts if needed
+        item.flavCount = 0;
+        item.score = 0;
+        m_cache.push_back(item);
     }
-  }
+    loadRdas();
+    m_cacheLoaded = true;
 }
 
-std::vector<FoodItem> FoodRepository::searchFoods(const QString &query) {
-  ensureCacheLoaded();
-  std::vector<FoodItem> results;
+void FoodRepository::loadRdas() {
+    m_rdas.clear();
+    QSqlDatabase db = DatabaseManager::instance().database();
+    QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+
+    // 1. Load Defaults from USDA
+    if (db.isOpen()) {
+        QSqlQuery query("SELECT id, rda FROM nutrients_overview", db);
+        while (query.next()) {
+            m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+        }
+    }
 
-  if (query.trimmed().isEmpty())
-    return results;
+    // 2. Load Overrides from User DB
+    if (userDb.isOpen()) {
+        QSqlQuery query("SELECT nutr_id, rda FROM rda WHERE profile_id = 1", userDb);
+        while (query.next()) {
+            m_rdas[query.value(0).toInt()] = query.value(1).toDouble();
+        }
+    }
+}
 
-  // Calculate scores
-  // create a temporary list of pointers or indices to sort?
-  // Copying might be expensive if cache is huge (8k items is fine though)
-
-  // Let's iterate and keep top matches.
-  struct ScoredItem {
-    const FoodItem *item;
-    int score;
-  };
-  std::vector<ScoredItem> scoredItems;
-  scoredItems.reserve(m_cache.size());
-
-  for (const auto &item : m_cache) {
-    int score = Utils::calculateFuzzyScore(query, item.description);
-    if (score > 40) { // Threshold
-      scoredItems.push_back({&item, score});
+std::vector<FoodItem> FoodRepository::searchFoods(const QString& query) {
+    ensureCacheLoaded();
+    std::vector<FoodItem> results;
+
+    if (query.trimmed().isEmpty()) return results;
+
+    // Calculate scores
+    // create a temporary list of pointers or indices to sort?
+    // Copying might be expensive if cache is huge (8k items is fine though)
+
+    // Let's iterate and keep top matches.
+    struct ScoredItem {
+        const FoodItem* item;
+        int score;
+    };
+    std::vector<ScoredItem> scoredItems;
+    scoredItems.reserve(m_cache.size());
+
+    for (const auto& item : m_cache) {
+        int score = Utils::calculateFuzzyScore(query, item.description);
+        if (score > 40) {  // Threshold
+            scoredItems.push_back({&item, score});
+        }
     }
-  }
-
-  // Sort by score desc
-  std::sort(scoredItems.begin(), scoredItems.end(),
-            [](const ScoredItem &a, const ScoredItem &b) {
-              return a.score > b.score;
-            });
-
-  // Take top 100
-  int count = 0;
-  std::vector<int> resultIds;
-  std::map<int, int> idToIndex;
-
-  for (const auto &si : scoredItems) {
-    if (count >= 100)
-      break;
-    FoodItem res = *si.item;
-    res.score = si.score;
-    // We will populate nutrients shortly
-    results.push_back(res);
-    resultIds.push_back(res.id);
-    idToIndex[res.id] = count;
-    count++;
-  }
-
-  // Batch fetch nutrients for these results
-  if (!resultIds.empty()) {
-    QSqlDatabase db = DatabaseManager::instance().database();
-    QStringList idStrings;
-    for (int id : resultIds)
-      idStrings << QString::number(id);
-
-    QString sql =
-        QString("SELECT n.food_id, n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
-                "FROM nut_data n "
-                "JOIN nutr_def d ON n.nutr_id = d.id "
-                "WHERE n.food_id IN (%1)")
-            .arg(idStrings.join(","));
-
-    QSqlQuery nutQuery(sql, db);
-    while (nutQuery.next()) {
-      int fid = nutQuery.value(0).toInt();
-      Nutrient nut;
-      nut.id = nutQuery.value(1).toInt();
-      nut.amount = nutQuery.value(2).toDouble();
-      nut.description = nutQuery.value(3).toString();
-      nut.unit = nutQuery.value(4).toString();
-
-      if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
-        nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
-      } else {
-        nut.rdaPercentage = 0.0;
-      }
-
-      if (idToIndex.count(fid) != 0U) {
-        results[idToIndex[fid]].nutrients.push_back(nut);
-      }
+
+    // Sort by score desc
+    std::sort(scoredItems.begin(), scoredItems.end(),
+              [](const ScoredItem& a, const ScoredItem& b) { return a.score > b.score; });
+
+    // Take top 100
+    int count = 0;
+    std::vector<int> resultIds;
+    std::map<int, int> idToIndex;
+
+    for (const auto& si : scoredItems) {
+        if (count >= 100) break;
+        FoodItem res = *si.item;
+        res.score = si.score;
+        // We will populate nutrients shortly
+        results.push_back(res);
+        resultIds.push_back(res.id);
+        idToIndex[res.id] = count;
+        count++;
     }
 
-    // Update counts based on actual data
-    for (auto &res : results) {
-      res.nutrientCount = static_cast<int>(res.nutrients.size());
-      // TODO: Logic for amino/flav counts if we have ranges of IDs
+    // Batch fetch nutrients for these results
+    if (!resultIds.empty()) {
+        QSqlDatabase db = DatabaseManager::instance().database();
+        QStringList idStrings;
+        for (int id : resultIds) idStrings << QString::number(id);
+
+        QString sql = QString(
+                          "SELECT n.food_id, n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
+                          "FROM nut_data n "
+                          "JOIN nutr_def d ON n.nutr_id = d.id "
+                          "WHERE n.food_id IN (%1)")
+                          .arg(idStrings.join(","));
+
+        QSqlQuery nutQuery(sql, db);
+        while (nutQuery.next()) {
+            int fid = nutQuery.value(0).toInt();
+            Nutrient nut;
+            nut.id = nutQuery.value(1).toInt();
+            nut.amount = nutQuery.value(2).toDouble();
+            nut.description = nutQuery.value(3).toString();
+            nut.unit = nutQuery.value(4).toString();
+
+            if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
+                nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
+            } else {
+                nut.rdaPercentage = 0.0;
+            }
+
+            if (idToIndex.count(fid) != 0U) {
+                results[idToIndex[fid]].nutrients.push_back(nut);
+            }
+        }
+
+        // Update counts based on actual data
+        for (auto& res : results) {
+            res.nutrientCount = static_cast<int>(res.nutrients.size());
+            // TODO: Logic for amino/flav counts if we have ranges of IDs
+        }
     }
-  }
 
-  return results;
+    return results;
 }
 
 std::vector<Nutrient> FoodRepository::getFoodNutrients(int foodId) {
-  std::vector<Nutrient> results;
-  QSqlDatabase db = DatabaseManager::instance().database();
+    std::vector<Nutrient> results;
+    QSqlDatabase db = DatabaseManager::instance().database();
 
-  if (!db.isOpen())
-    return results;
+    if (!db.isOpen()) return results;
+
+    QSqlQuery query(db);
+    if (!query.prepare("SELECT n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
+                       "FROM nut_data n "
+                       "JOIN nutr_def d ON n.nutr_id = d.id "
+                       "WHERE n.food_id = ?")) {
+        qCritical() << "Prepare failed:" << query.lastError().text();
+        return results;
+    }
 
-  QSqlQuery query(db);
-  if (!query.prepare("SELECT n.nutr_id, n.nutr_val, d.nutr_desc, d.unit "
-                     "FROM nut_data n "
-                     "JOIN nutr_def d ON n.nutr_id = d.id "
-                     "WHERE n.food_id = ?")) {
+    query.bindValue(0, foodId);
 
-    qCritical() << "Prepare failed:" << query.lastError().text();
-    return results;
-  }
+    if (query.exec()) {
+        while (query.next()) {
+            Nutrient nut;
+            nut.id = query.value(0).toInt();
+            nut.amount = query.value(1).toDouble();
+            nut.description = query.value(2).toString();
+            nut.unit = query.value(3).toString();
 
-  query.bindValue(0, foodId);
+            if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
+                nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
+            } else {
+                nut.rdaPercentage = 0.0;
+            }
 
-  if (query.exec()) {
-    while (query.next()) {
-      Nutrient nut;
-      nut.id = query.value(0).toInt();
-      nut.amount = query.value(1).toDouble();
-      nut.description = query.value(2).toString();
-      nut.unit = query.value(3).toString();
-
-      if (m_rdas.count(nut.id) != 0U && m_rdas[nut.id] > 0) {
-        nut.rdaPercentage = (nut.amount / m_rdas[nut.id]) * 100.0;
-      } else {
-        nut.rdaPercentage = 0.0;
-      }
-
-      results.push_back(nut);
-    }
+            results.push_back(nut);
+        }
 
-  } else {
-    qCritical() << "Nutrient query failed:" << query.lastError().text();
-  }
+    } else {
+        qCritical() << "Nutrient query failed:" << query.lastError().text();
+    }
 
-  return results;
+    return results;
 }
 
 std::vector<ServingWeight> FoodRepository::getFoodServings(int foodId) {
-  std::vector<ServingWeight> results;
-  QSqlDatabase db = DatabaseManager::instance().database();
-
-  if (!db.isOpen())
-    return results;
+    std::vector<ServingWeight> results;
+    QSqlDatabase db = DatabaseManager::instance().database();
 
-  QSqlQuery query(db);
-  if (!query.prepare("SELECT d.msre_desc, s.grams "
-                     "FROM serving s "
-                     "JOIN serv_desc d ON s.msre_id = d.id "
-                     "WHERE s.food_id = ?")) {
-    qCritical() << "Prepare servings failed:" << query.lastError().text();
-    return results;
-  }
+    if (!db.isOpen()) return results;
 
-  query.bindValue(0, foodId);
+    QSqlQuery query(db);
+    if (!query.prepare("SELECT d.msre_desc, s.grams "
+                       "FROM serving s "
+                       "JOIN serv_desc d ON s.msre_id = d.id "
+                       "WHERE s.food_id = ?")) {
+        qCritical() << "Prepare servings failed:" << query.lastError().text();
+        return results;
+    }
 
-  if (query.exec()) {
-    while (query.next()) {
-      ServingWeight sw;
-      sw.description = query.value(0).toString();
-      sw.grams = query.value(1).toDouble();
-      results.push_back(sw);
+    query.bindValue(0, foodId);
+
+    if (query.exec()) {
+        while (query.next()) {
+            ServingWeight sw;
+            sw.description = query.value(0).toString();
+            sw.grams = query.value(1).toDouble();
+            results.push_back(sw);
+        }
+    } else {
+        qCritical() << "Servings query failed:" << query.lastError().text();
     }
-  } else {
-    qCritical() << "Servings query failed:" << query.lastError().text();
-  }
 
-  return results;
+    return results;
 }
 
 std::map<int, double> FoodRepository::getNutrientRdas() {
-  ensureCacheLoaded();
-  return m_rdas;
+    ensureCacheLoaded();
+    return m_rdas;
 }
 
 void FoodRepository::updateRda(int nutrId, double value) {
-  QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
-  if (!userDb.isOpen())
-    return;
-
-  QSqlQuery query(userDb);
-  query.prepare("INSERT OR REPLACE INTO rda (profile_id, nutr_id, rda) "
-                "VALUES (1, ?, ?)");
-  query.bindValue(0, nutrId);
-  query.bindValue(1, value);
-
-  if (query.exec()) {
-    m_rdas[nutrId] = value;
-  } else {
-    qCritical() << "Failed to update RDA:" << query.lastError().text();
-  }
+    QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+    if (!userDb.isOpen()) return;
+
+    QSqlQuery query(userDb);
+    query.prepare(
+        "INSERT OR REPLACE INTO rda (profile_id, nutr_id, rda) "
+        "VALUES (1, ?, ?)");
+    query.bindValue(0, nutrId);
+    query.bindValue(1, value);
+
+    if (query.exec()) {
+        m_rdas[nutrId] = value;
+    } else {
+        qCritical() << "Failed to update RDA:" << query.lastError().text();
+    }
 }
diff --git a/src/db/mealrepository.cpp b/src/db/mealrepository.cpp
new file mode 100644 (file)
index 0000000..0fd1e14
--- /dev/null
@@ -0,0 +1,152 @@
+#include "db/mealrepository.h"
+
+#include <QDateTime>
+#include <QDebug>
+#include <QSqlError>
+#include <QSqlQuery>
+#include <QVariant>
+
+#include "db/databasemanager.h"
+
+MealRepository::MealRepository() {}
+
+void MealRepository::ensureMealNamesLoaded() {
+    if (!m_mealNamesCache.empty()) return;
+
+    QSqlDatabase db = DatabaseManager::instance().userDatabase();
+    if (!db.isOpen()) return;
+
+    QSqlQuery query("SELECT id, name FROM meal_name", db);
+    while (query.next()) {
+        m_mealNamesCache[query.value(0).toInt()] = query.value(1).toString();
+    }
+}
+
+std::map<int, QString> MealRepository::getMealNames() {
+    ensureMealNamesLoaded();
+    return m_mealNamesCache;
+}
+
+void MealRepository::addFoodLog(int foodId, double grams, int mealId, QDate date) {
+    QSqlDatabase db = DatabaseManager::instance().userDatabase();
+    if (!db.isOpen()) return;
+
+    // Use current time if today, otherwise noon of target date
+    qint64 timestamp;
+    if (date == QDate::currentDate()) {
+        timestamp = QDateTime::currentSecsSinceEpoch();
+    } else {
+        timestamp = date.startOfDay().toSecsSinceEpoch() + 43200;  // Noon
+    }
+
+    QSqlQuery query(db);
+    query.prepare(
+        "INSERT INTO log_food (profile_id, date, meal_id, food_id, msre_id, amt) "
+        "VALUES (1, ?, ?, ?, 0, ?)");  // msre_id 0 for default/grams
+    query.addBindValue(timestamp);
+    query.addBindValue(mealId);
+    query.addBindValue(foodId);
+    query.addBindValue(grams);
+
+    if (!query.exec()) {
+        qCritical() << "Failed to add food log:" << query.lastError().text();
+    }
+}
+
+std::vector<MealLogItem> MealRepository::getDailyLogs(QDate date) {
+    std::vector<MealLogItem> results;
+    QSqlDatabase userDb = DatabaseManager::instance().userDatabase();
+    QSqlDatabase mainDb = DatabaseManager::instance().database();
+
+    if (!userDb.isOpen()) return results;
+
+    ensureMealNamesLoaded();
+
+    qint64 startOfDay = date.startOfDay().toSecsSinceEpoch();
+    qint64 endOfDay = date.endOfDay().toSecsSinceEpoch();
+
+    QSqlQuery query(userDb);
+    query.prepare(
+        "SELECT id, food_id, meal_id, amt FROM log_food "
+        "WHERE date >= ? AND date <= ? AND profile_id = 1");
+    query.addBindValue(startOfDay);
+    query.addBindValue(endOfDay);
+
+    std::vector<int> foodIds;
+
+    if (query.exec()) {
+        while (query.next()) {
+            MealLogItem item;
+            item.id = query.value(0).toInt();
+            item.foodId = query.value(1).toInt();
+            item.mealId = query.value(2).toInt();
+            item.grams = query.value(3).toDouble();
+
+            if (m_mealNamesCache.count(item.mealId)) {
+                item.mealName = m_mealNamesCache[item.mealId];
+            } else {
+                item.mealName = "Unknown";
+            }
+
+            results.push_back(item);
+            foodIds.push_back(item.foodId);
+        }
+    } else {
+        qCritical() << "Failed to fetch daily logs:" << query.lastError().text();
+    }
+
+    // Hydrate food names from Main DB
+    if (!foodIds.empty() && mainDb.isOpen()) {
+        QStringList idStrings;
+        for (int id : foodIds) idStrings << QString::number(id);
+
+        // Simple name fetch
+        // Optimization: Could use FoodRepository cache if available, but direct
+        // query is safe here
+        QString sql =
+            QString("SELECT id, long_desc FROM food_des WHERE id IN (%1)").arg(idStrings.join(","));
+        QSqlQuery nameQuery(sql, mainDb);
+
+        std::map<int, QString> names;
+        while (nameQuery.next()) {
+            names[nameQuery.value(0).toInt()] = nameQuery.value(1).toString();
+        }
+
+        for (auto& item : results) {
+            if (names.count(item.foodId)) {
+                item.foodName = names[item.foodId];
+            } else {
+                item.foodName = "Unknown Food";  // Should not happen if DBs consistent
+            }
+        }
+    }
+
+    return results;
+}
+
+void MealRepository::clearDailyLogs(QDate date) {
+    QSqlDatabase db = DatabaseManager::instance().userDatabase();
+    if (!db.isOpen()) return;
+
+    qint64 startOfDay = date.startOfDay().toSecsSinceEpoch();
+    qint64 endOfDay = date.endOfDay().toSecsSinceEpoch();
+
+    QSqlQuery query(db);
+    query.prepare("DELETE FROM log_food WHERE date >= ? AND date <= ? AND profile_id = 1");
+    query.addBindValue(startOfDay);
+    query.addBindValue(endOfDay);
+
+    if (!query.exec()) {
+        qCritical() << "Failed to clear daily logs:" << query.lastError().text();
+    }
+}
+
+void MealRepository::removeLogEntry(int logId) {
+    QSqlDatabase db = DatabaseManager::instance().userDatabase();
+    if (!db.isOpen()) return;
+
+    QSqlQuery query(db);
+    query.prepare("DELETE FROM log_food WHERE id = ?");
+    query.addBindValue(logId);
+    query.exec();
+}
index 27bfce43f40466b8353a7ce208580434eca3940f..126ffc89d778b9bb201bd9e9c5f1a01109e272cb 100644 (file)
@@ -1,5 +1,3 @@
-#include "db/databasemanager.h"
-#include "mainwindow.h"
 #include <QApplication>
 #include <QDebug>
 #include <QDir>
@@ -9,74 +7,75 @@
 #include <QMessageBox>
 #include <QStandardPaths>
 
-int main(int argc, char *argv[]) {
-  QApplication app(argc, argv);
-  QApplication::setApplicationName("Nutra");
-  QApplication::setOrganizationName("NutraTech");
-  QApplication::setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
+#include "db/databasemanager.h"
+#include "mainwindow.h"
 
-  // Connect to database
-  // Search order:
-  // 1. Environment variable NUTRA_DB_PATH
-  // 2. Local user data: ~/.local/share/nutra/usda.sqlite3
-  // 3. System install: /usr/local/share/nutra/usda.sqlite3
-  // 4. System install: /usr/share/nutra/usda.sqlite3
-  // 5. Legacy: ~/.nutra/usda.sqlite3
+int main(int argc, char* argv[]) {
+    QApplication app(argc, argv);
+    QApplication::setApplicationName("Nutra");
+    QApplication::setOrganizationName("NutraTech");
+    QApplication::setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
 
-  QStringList searchPaths;
-  QString envPath = qEnvironmentVariable("NUTRA_DB_PATH");
-  if (!envPath.isEmpty())
-    searchPaths << envPath;
+    // Connect to database
+    // Search order:
+    // 1. Environment variable NUTRA_DB_PATH
+    // 2. Local user data: ~/.local/share/nutra/usda.sqlite3
+    // 3. System install: /usr/local/share/nutra/usda.sqlite3
+    // 4. System install: /usr/share/nutra/usda.sqlite3
+    // 5. Legacy: ~/.nutra/usda.sqlite3
 
-  searchPaths << QStandardPaths::locate(QStandardPaths::AppDataLocation,
-                                        "usda.sqlite3",
-                                        QStandardPaths::LocateFile);
-  searchPaths << QDir::homePath() + "/.local/share/nutra/usda.sqlite3";
-  searchPaths << "/usr/local/share/nutra/usda.sqlite3";
-  searchPaths << "/usr/share/nutra/usda.sqlite3";
-  searchPaths << QDir::homePath() + "/.nutra/usda.sqlite3";
+    QStringList searchPaths;
+    QString envPath = qEnvironmentVariable("NUTRA_DB_PATH");
+    if (!envPath.isEmpty()) searchPaths << envPath;
 
-  QString dbPath;
-  for (const QString &path : searchPaths) {
-    if (!path.isEmpty() && QFileInfo::exists(path)) {
-      dbPath = path;
-      break;
-    }
-  }
+    searchPaths << QStandardPaths::locate(QStandardPaths::AppDataLocation, "usda.sqlite3",
+                                          QStandardPaths::LocateFile);
+    searchPaths << QDir::homePath() + "/.local/share/nutra/usda.sqlite3";
+    searchPaths << "/usr/local/share/nutra/usda.sqlite3";
+    searchPaths << "/usr/share/nutra/usda.sqlite3";
+    searchPaths << QDir::homePath() + "/.nutra/usda.sqlite3";
 
-  if (dbPath.isEmpty()) {
-    qWarning() << "Database not found in standard locations.";
-    QMessageBox::StandardButton resBtn = QMessageBox::question(
-        nullptr, "Database Not Found",
-        "The Nutrient database (usda.sqlite3) was not found.\nWould you like "
-        "to browse for it manually?",
-        QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes);
-
-    if (resBtn == QMessageBox::Yes) {
-      dbPath = QFileDialog::getOpenFileName(
-          nullptr, "Find usda.sqlite3", QDir::homePath() + "/.nutra",
-          "SQLite Databases (*.sqlite3 *.db)");
+    QString dbPath;
+    for (const QString& path : searchPaths) {
+        if (!path.isEmpty() && QFileInfo::exists(path)) {
+            dbPath = path;
+            break;
+        }
     }
 
     if (dbPath.isEmpty()) {
-      return 1; // User cancelled or still not found
+        qWarning() << "Database not found in standard locations.";
+        QMessageBox::StandardButton resBtn = QMessageBox::question(
+            nullptr, "Database Not Found",
+            "The Nutrient database (usda.sqlite3) was not found.\nWould you like "
+            "to browse for it manually?",
+            QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes);
+
+        if (resBtn == QMessageBox::Yes) {
+            dbPath = QFileDialog::getOpenFileName(nullptr, "Find usda.sqlite3",
+                                                  QDir::homePath() + "/.nutra",
+                                                  "SQLite Databases (*.sqlite3 *.db)");
+        }
+
+        if (dbPath.isEmpty()) {
+            return 1;  // User cancelled or still not found
+        }
     }
-  }
 
-  if (!DatabaseManager::instance().connect(dbPath)) {
-    QString errorMsg =
-        QString("Failed to connect to database at:\n%1\n\nPlease ensure the "
-                "database file exists or browse for a valid SQLite file.")
-            .arg(dbPath);
-    qCritical() << errorMsg;
-    QMessageBox::critical(nullptr, "Database Error", errorMsg);
-    // Let's try to let the user browse one more time before giving up
-    return 1;
-  }
-  qDebug() << "Connected to database at:" << dbPath;
+    if (!DatabaseManager::instance().connect(dbPath)) {
+        QString errorMsg = QString(
+                               "Failed to connect to database at:\n%1\n\nPlease ensure the "
+                               "database file exists or browse for a valid SQLite file.")
+                               .arg(dbPath);
+        qCritical() << errorMsg;
+        QMessageBox::critical(nullptr, "Database Error", errorMsg);
+        // Let's try to let the user browse one more time before giving up
+        return 1;
+    }
+    qDebug() << "Connected to database at:" << dbPath;
 
-  MainWindow window;
-  window.show();
+    MainWindow window;
+    window.show();
 
-  return QApplication::exec();
+    return QApplication::exec();
 }
index 9c6d2e6ff395fae9bd2355b2b80e6817645c19ba..68af6972bb69d3fa5cf4b9d01de86abc92a32fad 100644 (file)
@@ -1,6 +1,5 @@
 #include "mainwindow.h"
-#include "db/databasemanager.h"
-#include "widgets/rdasettingswidget.h"
+
 #include <QAction>
 #include <QDebug>
 #include <QFileDialog>
 #include <QVBoxLayout>
 #include <QWidget>
 
-MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
-  for (int i = 0; i < MaxRecentFiles; ++i) {
-    recentFileActions[i] = new QAction(this);
-    recentFileActions[i]->setVisible(false);
-    connect(recentFileActions[i], &QAction::triggered, this,
-            &MainWindow::onRecentFileClick);
-  }
-  setupUi();
-  updateRecentFileActions();
+#include "db/databasemanager.h"
+#include "widgets/rdasettingswidget.h"
+
+MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
+    for (int i = 0; i < MaxRecentFiles; ++i) {
+        recentFileActions[i] = new QAction(this);
+        recentFileActions[i]->setVisible(false);
+        connect(recentFileActions[i], &QAction::triggered, this, &MainWindow::onRecentFileClick);
+    }
+    setupUi();
+    updateRecentFileActions();
 }
 
 MainWindow::~MainWindow() = default;
 
 void MainWindow::setupUi() {
-  setWindowTitle("Nutrient Coach");
-  setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
-  resize(1000, 700);
-
-  // File Menu
-  auto *fileMenu = menuBar()->addMenu("&File");
-  auto *openDbAction = fileMenu->addAction("&Open Database...");
-  recentFilesMenu = fileMenu->addMenu("Recent Databases");
-  fileMenu->addSeparator();
-  auto *exitAction = fileMenu->addAction("E&xit");
-  connect(openDbAction, &QAction::triggered, this, &MainWindow::onOpenDatabase);
-  connect(exitAction, &QAction::triggered, this, &QWidget::close);
-
-  for (int i = 0; i < MaxRecentFiles; ++i)
-    recentFilesMenu->addAction(recentFileActions[i]);
-
-  // Edit Menu
-  QMenu *editMenu = menuBar()->addMenu("Edit");
-  QAction *rdaAction = editMenu->addAction("RDA Settings");
-  connect(rdaAction, &QAction::triggered, this, [this]() {
-    RDASettingsWidget dlg(repository, this);
-    dlg.exec();
-  });
-
-  QAction *settingsAction = editMenu->addAction("Settings");
-  connect(settingsAction, &QAction::triggered, this, &MainWindow::onSettings);
-
-  // Help Menu
-  auto *helpMenu = menuBar()->addMenu("&Help");
-  auto *aboutAction = helpMenu->addAction("&About");
-  connect(aboutAction, &QAction::triggered, this, &MainWindow::onAbout);
-
-  auto *centralWidget = new QWidget(this);
-  setCentralWidget(centralWidget);
-
-  auto *mainLayout = new QVBoxLayout(centralWidget);
-
-  tabs = new QTabWidget(this);
-  mainLayout->addWidget(tabs);
-
-  // Search Tab
-  searchWidget = new SearchWidget(this);
-  tabs->addTab(searchWidget, "Search Foods");
-
-  // Connect signal
-  connect(searchWidget, &SearchWidget::foodSelected, this,
-          [=](int foodId, const QString &foodName) {
-            qDebug() << "Selected food:" << foodName << "(" << foodId << ")";
-            detailsWidget->loadFood(foodId, foodName);
-            tabs->setCurrentWidget(detailsWidget);
-          });
-
-  connect(searchWidget, &SearchWidget::addToMealRequested, this,
-          [=](int foodId, const QString &foodName, double grams) {
-            mealWidget->addFood(foodId, foodName, grams);
-            tabs->setCurrentWidget(mealWidget);
-          });
-
-  // Analysis Tab
-  detailsWidget = new DetailsWidget(this);
-  tabs->addTab(detailsWidget, "Analyze");
-
-  // Meal Tab
-  mealWidget = new MealWidget(this);
-  tabs->addTab(mealWidget, "Meal Tracker");
-
-  // Connect Analysis -> Meal
-  connect(detailsWidget, &DetailsWidget::addToMeal, this,
-          [=](int foodId, const QString &foodName, double grams) {
-            mealWidget->addFood(foodId, foodName, grams);
-            // Optional: switch tab?
-            // tabs->setCurrentWidget(mealWidget);
-          });
+    setWindowTitle("Nutrient Coach");
+    setWindowIcon(QIcon(":/resources/nutrition_icon-no_bg.png"));
+    resize(1000, 700);
+
+    // File Menu
+    auto* fileMenu = menuBar()->addMenu("&File");
+    auto* openDbAction = fileMenu->addAction("&Open Database...");
+    recentFilesMenu = fileMenu->addMenu("Recent Databases");
+    fileMenu->addSeparator();
+    auto* exitAction = fileMenu->addAction("E&xit");
+    connect(openDbAction, &QAction::triggered, this, &MainWindow::onOpenDatabase);
+    connect(exitAction, &QAction::triggered, this, &QWidget::close);
+
+    for (int i = 0; i < MaxRecentFiles; ++i) recentFilesMenu->addAction(recentFileActions[i]);
+
+    // Edit Menu
+    QMenu* editMenu = menuBar()->addMenu("Edit");
+    QAction* rdaAction = editMenu->addAction("RDA Settings");
+    connect(rdaAction, &QAction::triggered, this, [this]() {
+        RDASettingsWidget dlg(repository, this);
+        dlg.exec();
+    });
+
+    QAction* settingsAction = editMenu->addAction("Settings");
+    connect(settingsAction, &QAction::triggered, this, &MainWindow::onSettings);
+
+    // Help Menu
+    auto* helpMenu = menuBar()->addMenu("&Help");
+    auto* aboutAction = helpMenu->addAction("&About");
+    connect(aboutAction, &QAction::triggered, this, &MainWindow::onAbout);
+
+    auto* centralWidget = new QWidget(this);
+    setCentralWidget(centralWidget);
+
+    auto* mainLayout = new QVBoxLayout(centralWidget);
+
+    tabs = new QTabWidget(this);
+    mainLayout->addWidget(tabs);
+
+    // Search Tab
+    searchWidget = new SearchWidget(this);
+    tabs->addTab(searchWidget, "Search Foods");
+
+    // Connect signal
+    connect(searchWidget, &SearchWidget::foodSelected, this,
+            [=](int foodId, const QString& foodName) {
+                qDebug() << "Selected food:" << foodName << "(" << foodId << ")";
+                detailsWidget->loadFood(foodId, foodName);
+                tabs->setCurrentWidget(detailsWidget);
+            });
+
+    connect(searchWidget, &SearchWidget::addToMealRequested, this,
+            [=](int foodId, const QString& foodName, double grams) {
+                mealWidget->addFood(foodId, foodName, grams);
+                tabs->setCurrentWidget(mealWidget);
+            });
+
+    // Analysis Tab
+    detailsWidget = new DetailsWidget(this);
+    tabs->addTab(detailsWidget, "Analyze");
+
+    // Meal Tab
+    mealWidget = new MealWidget(this);
+    tabs->addTab(mealWidget, "Meal Tracker");
+
+    // Connect Analysis -> Meal
+    connect(detailsWidget, &DetailsWidget::addToMeal, this,
+            [=](int foodId, const QString& foodName, double grams) {
+                mealWidget->addFood(foodId, foodName, grams);
+                // Optional: switch tab?
+                // tabs->setCurrentWidget(mealWidget);
+            });
 }
 
 void MainWindow::onOpenDatabase() {
-  QString fileName = QFileDialog::getOpenFileName(
-      this, "Open USDA Database", QDir::homePath() + "/.nutra",
-      "SQLite Databases (*.sqlite3 *.db)");
-
-  if (!fileName.isEmpty()) {
-    if (DatabaseManager::instance().connect(fileName)) {
-      qDebug() << "Switched to database:" << fileName;
-      addToRecentFiles(fileName);
-      // In a real app, we'd emit a signal or refresh all widgets
-      // For now, let's just log and show success
-      QMessageBox::information(this, "Database Opened",
-                               "Successfully connected to: " + fileName);
-    } else {
-      QMessageBox::critical(this, "Database Error",
-                            "Failed to connect to the database.");
+    QString fileName =
+        QFileDialog::getOpenFileName(this, "Open USDA Database", QDir::homePath() + "/.nutra",
+                                     "SQLite Databases (*.sqlite3 *.db)");
+
+    if (!fileName.isEmpty()) {
+        if (DatabaseManager::instance().connect(fileName)) {
+            qDebug() << "Switched to database:" << fileName;
+            addToRecentFiles(fileName);
+            // In a real app, we'd emit a signal or refresh all widgets
+            // For now, let's just log and show success
+            QMessageBox::information(this, "Database Opened",
+                                     "Successfully connected to: " + fileName);
+        } else {
+            QMessageBox::critical(this, "Database Error", "Failed to connect to the database.");
+        }
     }
-  }
 }
 
 void MainWindow::onRecentFileClick() {
-  auto *action = qobject_cast<QAction *>(sender());
-  if (action != nullptr) {
-    QString fileName = action->data().toString();
-    if (DatabaseManager::instance().connect(fileName)) {
-      qDebug() << "Switched to database (recent):" << fileName;
-      addToRecentFiles(fileName);
-      QMessageBox::information(this, "Database Opened",
-                               "Successfully connected to: " + fileName);
-    } else {
-      QMessageBox::critical(this, "Database Error",
-                            "Failed to connect to: " + fileName);
+    auto* action = qobject_cast<QAction*>(sender());
+    if (action != nullptr) {
+        QString fileName = action->data().toString();
+        if (DatabaseManager::instance().connect(fileName)) {
+            qDebug() << "Switched to database (recent):" << fileName;
+            addToRecentFiles(fileName);
+            QMessageBox::information(this, "Database Opened",
+                                     "Successfully connected to: " + fileName);
+        } else {
+            QMessageBox::critical(this, "Database Error", "Failed to connect to: " + fileName);
+        }
     }
-  }
 }
 
 void MainWindow::updateRecentFileActions() {
-  QSettings settings("NutraTech", "Nutra");
-  QStringList files = settings.value("recentFiles").toStringList();
-
-  int numRecentFiles = qMin(files.size(), MaxRecentFiles);
-
-  for (int i = 0; i < numRecentFiles; ++i) {
-    QString text =
-        QString("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).fileName());
-    recentFileActions[i]->setText(text);
-    recentFileActions[i]->setData(files[i]);
-    recentFileActions[i]->setVisible(true);
-  }
-  for (int i = numRecentFiles; i < MaxRecentFiles; ++i)
-    recentFileActions[i]->setVisible(false);
-
-  recentFilesMenu->setEnabled(numRecentFiles > 0);
+    QSettings settings("NutraTech", "Nutra");
+    QStringList files = settings.value("recentFiles").toStringList();
+
+    int numRecentFiles = qMin(files.size(), MaxRecentFiles);
+
+    for (int i = 0; i < numRecentFiles; ++i) {
+        QString text = QString("&%1 %2").arg(i + 1).arg(QFileInfo(files[i]).fileName());
+        recentFileActions[i]->setText(text);
+        recentFileActions[i]->setData(files[i]);
+        recentFileActions[i]->setVisible(true);
+    }
+    for (int i = numRecentFiles; i < MaxRecentFiles; ++i) recentFileActions[i]->setVisible(false);
+
+    recentFilesMenu->setEnabled(numRecentFiles > 0);
 }
 
-void MainWindow::addToRecentFiles(const QString &path) {
-  QSettings settings("NutraTech", "Nutra");
-  QStringList files = settings.value("recentFiles").toStringList();
-  files.removeAll(path);
-  files.prepend(path);
-  while (files.size() > MaxRecentFiles)
-    files.removeLast();
+void MainWindow::addToRecentFiles(const QString& path) {
+    QSettings settings("NutraTech", "Nutra");
+    QStringList files = settings.value("recentFiles").toStringList();
+    files.removeAll(path);
+    files.prepend(path);
+    while (files.size() > MaxRecentFiles) files.removeLast();
 
-  settings.setValue("recentFiles", files);
-  updateRecentFileActions();
+    settings.setValue("recentFiles", files);
+    updateRecentFileActions();
 }
 
 void MainWindow::onSettings() {
-  QMessageBox::information(this, "Settings", "Settings dialog coming soon!");
+    QMessageBox::information(this, "Settings", "Settings dialog coming soon!");
 }
 
 void MainWindow::onAbout() {
-  QMessageBox::about(
-      this, "About Nutrient Coach",
-      QString("<h3>Nutrient Coach %1</h3>"
-              "<p>A C++/Qt application for tracking nutrition.</p>"
-              "<p>Homepage: <a "
-              "href=\"https://github.com/nutratech/gui\">https://github.com/"
-              "nutratech/gui</a></p>")
-          .arg(NUTRA_VERSION_STRING));
+    QMessageBox::about(this, "About Nutrient Coach",
+                       QString("<h3>Nutrient Coach %1</h3>"
+                               "<p>A C++/Qt application for tracking nutrition.</p>"
+                               "<p>Homepage: <a "
+                               "href=\"https://github.com/nutratech/gui\">https://github.com/"
+                               "nutratech/gui</a></p>")
+                           .arg(NUTRA_VERSION_STRING));
 }
index a6d16350c039926d6a8ce21d49189d77c32ec41c..4c7bf21fbbf90033b79172f065c36d2cace4f061 100644 (file)
 #include "utils/string_utils.h"
+
 #include <QRegularExpression>
 #include <QStringList>
 #include <QtGlobal>
-#include <algorithm> // Required for std::max
+#include <algorithm>  // Required for std::max
 #include <cmath>
 
 #ifndef QT_VERSION_CHECK
-#define QT_VERSION_CHECK(major, minor, patch)                                  \
-  ((major << 16) | (minor << 8) | (patch))
+#define QT_VERSION_CHECK(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
 #endif
 
 namespace Utils {
 
-int levenshteinDistance(const QString &s1, const QString &s2) {
-  const auto m = s1.length();
-  const auto n = s2.length();
-
-  std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1));
-
-  for (int i = 0; i <= m; ++i) {
-    dp[i][0] = i;
-  }
-  for (int j = 0; j <= n; ++j) {
-    dp[0][j] = j;
-  }
-
-  for (int i = 1; i <= m; ++i) {
-    for (int j = 1; j <= n; ++j) {
-      if (s1[i - 1] == s2[j - 1]) {
-        dp[i][j] = dp[i - 1][j - 1];
-      } else {
-        dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
-      }
+int levenshteinDistance(const QString& s1, const QString& s2) {
+    const auto m = s1.length();
+    const auto n = s2.length();
+
+    std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1));
+
+    for (int i = 0; i <= m; ++i) {
+        dp[i][0] = i;
+    }
+    for (int j = 0; j <= n; ++j) {
+        dp[0][j] = j;
+    }
+
+    for (int i = 1; i <= m; ++i) {
+        for (int j = 1; j <= n; ++j) {
+            if (s1[i - 1] == s2[j - 1]) {
+                dp[i][j] = dp[i - 1][j - 1];
+            } else {
+                dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
+            }
+        }
     }
-  }
 
-  return dp[m][n];
+    return dp[m][n];
 }
 
-int calculateFuzzyScore(const QString &query, const QString &target) {
-  if (query.isEmpty()) {
-    return 0;
-  }
-  if (target.isEmpty()) {
-    return 0;
-  }
-
-  QString q = query.toLower();
-  QString t = target.toLower();
-
-  // 1. Exact match bonus
-  if (t == q) {
-    return 100;
-  }
-
-  // 2. Contains match bonus (very strong signal)
-  if (t.contains(q)) {
-    return 90; // Base score for containing the string
-  }
-
-  // 3. Token-based matching (handling "grass fed" vs "beef, grass-fed")
-  static const QRegularExpression regex("[\\s,-]+");
+int calculateFuzzyScore(const QString& query, const QString& target) {
+    if (query.isEmpty()) {
+        return 0;
+    }
+    if (target.isEmpty()) {
+        return 0;
+    }
+
+    QString q = query.toLower();
+    QString t = target.toLower();
+
+    // 1. Exact match bonus
+    if (t == q) {
+        return 100;
+    }
+
+    // 2. Contains match bonus (very strong signal)
+    if (t.contains(q)) {
+        return 90;  // Base score for containing the string
+    }
+
+    // 3. Token-based matching (handling "grass fed" vs "beef, grass-fed")
+    static const QRegularExpression regex("[\\s,-]+");
 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
-  auto behavior = Qt::SkipEmptyParts;
+    auto behavior = Qt::SkipEmptyParts;
 #else
-  auto behavior = QString::SkipEmptyParts;
+    auto behavior = QString::SkipEmptyParts;
 #endif
-  QStringList queryTokens = q.split(regex, behavior);
-  QStringList targetTokens = t.split(regex, behavior);
-
-  int totalScore = 0;
-  int matchedTokens = 0;
-
-  for (const QString &qToken : queryTokens) {
-    int maxTokenScore = 0;
-    for (const QString &tToken : targetTokens) {
-      int dist = levenshteinDistance(qToken, tToken);
-      int maxLen = static_cast<int>(std::max(qToken.length(), tToken.length()));
-      if (maxLen == 0)
-        continue;
-
-      int score = 0;
-      if (tToken.startsWith(qToken)) {
-        score = 95; // Prefix match is very good
-      } else {
-        double ratio = 1.0 - (static_cast<double>(dist) / maxLen);
-        score = static_cast<int>(ratio * 100);
-      }
-
-      maxTokenScore = std::max(maxTokenScore, score);
+    QStringList queryTokens = q.split(regex, behavior);
+    QStringList targetTokens = t.split(regex, behavior);
+
+    int totalScore = 0;
+    int matchedTokens = 0;
+
+    for (const QString& qToken : queryTokens) {
+        int maxTokenScore = 0;
+        for (const QString& tToken : targetTokens) {
+            int dist = levenshteinDistance(qToken, tToken);
+            int maxLen = static_cast<int>(std::max(qToken.length(), tToken.length()));
+            if (maxLen == 0) continue;
+
+            int score = 0;
+            if (tToken.startsWith(qToken)) {
+                score = 95;  // Prefix match is very good
+            } else {
+                double ratio = 1.0 - (static_cast<double>(dist) / maxLen);
+                score = static_cast<int>(ratio * 100);
+            }
+
+            maxTokenScore = std::max(maxTokenScore, score);
+        }
+        totalScore += maxTokenScore;
+        if (maxTokenScore > 60) matchedTokens++;
     }
-    totalScore += maxTokenScore;
-    if (maxTokenScore > 60)
-      matchedTokens++;
-  }
 
-  if (queryTokens.isEmpty()) {
-    return 0;
-  }
+    if (queryTokens.isEmpty()) {
+        return 0;
+    }
 
-  int averageScore = static_cast<int>(totalScore / queryTokens.size());
+    int averageScore = static_cast<int>(totalScore / queryTokens.size());
 
-  // Penalize if not all tokens matched somewhat well
-  if (matchedTokens < queryTokens.size()) {
-    averageScore -= 20;
-  }
+    // Penalize if not all tokens matched somewhat well
+    if (matchedTokens < queryTokens.size()) {
+        averageScore -= 20;
+    }
 
-  return std::max(0, averageScore);
+    return std::max(0, averageScore);
 }
 
-} // namespace Utils
+}  // namespace Utils
index c775d05c54df2d48927923c6a483fdc0312329a9..d3041df47e3cf968dd82a5f2b9433f35daabbadd 100644 (file)
@@ -1,63 +1,61 @@
 #include "widgets/detailswidget.h"
+
 #include <QDebug>
 #include <QHBoxLayout>
 #include <QHeaderView>
 #include <QVBoxLayout>
 
-DetailsWidget::DetailsWidget(QWidget *parent)
-    : QWidget(parent), currentFoodId(-1) {
-  auto *layout = new QVBoxLayout(this);
-
-  // Header
-  auto *headerLayout = new QHBoxLayout();
-  nameLabel = new QLabel("No food selected", this);
-  QFont font = nameLabel->font();
-  font.setPointSize(14);
-  font.setBold(true);
-  nameLabel->setFont(font);
-
-  addButton = new QPushButton("Add to Meal", this);
-  addButton->setEnabled(false);
-  connect(addButton, &QPushButton::clicked, this, &DetailsWidget::onAddClicked);
-
-  headerLayout->addWidget(nameLabel);
-  headerLayout->addStretch();
-  headerLayout->addWidget(addButton);
-  layout->addLayout(headerLayout);
-
-  // Nutrients Table
-  nutrientsTable = new QTableWidget(this);
-  nutrientsTable->setColumnCount(3);
-  nutrientsTable->setHorizontalHeaderLabels({"Nutrient", "Amount", "Unit"});
-  nutrientsTable->horizontalHeader()->setSectionResizeMode(
-      0, QHeaderView::Stretch);
-  nutrientsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
-  layout->addWidget(nutrientsTable);
+DetailsWidget::DetailsWidget(QWidget* parent) : QWidget(parent), currentFoodId(-1) {
+    auto* layout = new QVBoxLayout(this);
+
+    // Header
+    auto* headerLayout = new QHBoxLayout();
+    nameLabel = new QLabel("No food selected", this);
+    QFont font = nameLabel->font();
+    font.setPointSize(14);
+    font.setBold(true);
+    nameLabel->setFont(font);
+
+    addButton = new QPushButton("Add to Meal", this);
+    addButton->setEnabled(false);
+    connect(addButton, &QPushButton::clicked, this, &DetailsWidget::onAddClicked);
+
+    headerLayout->addWidget(nameLabel);
+    headerLayout->addStretch();
+    headerLayout->addWidget(addButton);
+    layout->addLayout(headerLayout);
+
+    // Nutrients Table
+    nutrientsTable = new QTableWidget(this);
+    nutrientsTable->setColumnCount(3);
+    nutrientsTable->setHorizontalHeaderLabels({"Nutrient", "Amount", "Unit"});
+    nutrientsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+    nutrientsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+    layout->addWidget(nutrientsTable);
 }
 
-void DetailsWidget::loadFood(int foodId, const QString &foodName) {
-  currentFoodId = foodId;
-  currentFoodName = foodName;
-  nameLabel->setText(foodName + QString(" (ID: %1)").arg(foodId));
-  addButton->setEnabled(true);
+void DetailsWidget::loadFood(int foodId, const QStringfoodName) {
+    currentFoodId = foodId;
+    currentFoodName = foodName;
+    nameLabel->setText(foodName + QString(" (ID: %1)").arg(foodId));
+    addButton->setEnabled(true);
 
-  nutrientsTable->setRowCount(0);
+    nutrientsTable->setRowCount(0);
 
-  std::vector<Nutrient> nutrients = repository.getFoodNutrients(foodId);
+    std::vector<Nutrient> nutrients = repository.getFoodNutrients(foodId);
 
-  nutrientsTable->setRowCount(static_cast<int>(nutrients.size()));
-  for (int i = 0; i < static_cast<int>(nutrients.size()); ++i) {
-    const auto &nut = nutrients[i];
-    nutrientsTable->setItem(i, 0, new QTableWidgetItem(nut.description));
-    nutrientsTable->setItem(i, 1,
-                            new QTableWidgetItem(QString::number(nut.amount)));
-    nutrientsTable->setItem(i, 2, new QTableWidgetItem(nut.unit));
-  }
+    nutrientsTable->setRowCount(static_cast<int>(nutrients.size()));
+    for (int i = 0; i < static_cast<int>(nutrients.size()); ++i) {
+        const auto& nut = nutrients[i];
+        nutrientsTable->setItem(i, 0, new QTableWidgetItem(nut.description));
+        nutrientsTable->setItem(i, 1, new QTableWidgetItem(QString::number(nut.amount)));
+        nutrientsTable->setItem(i, 2, new QTableWidgetItem(nut.unit));
+    }
 }
 
 void DetailsWidget::onAddClicked() {
-  if (currentFoodId != -1) {
-    // Default 100g
-    emit addToMeal(currentFoodId, currentFoodName, 100.0);
-  }
+    if (currentFoodId != -1) {
+        // Default 100g
+        emit addToMeal(currentFoodId, currentFoodName, 100.0);
+    }
 }
index 9078ebd47a4f925a471ade33c0169782611484be..2921932591a436d54b684c1af9a53fab5996afb7 100644 (file)
 #include "widgets/mealwidget.h"
+
 #include <QDebug>
 #include <QHBoxLayout>
 #include <QHeaderView>
 #include <QLabel>
 #include <QVBoxLayout>
 
-MealWidget::MealWidget(QWidget *parent) : QWidget(parent) {
-  auto *layout = new QVBoxLayout(this);
-
-  // Items List
-  layout->addWidget(new QLabel("Meal Composition", this));
-  itemsTable = new QTableWidget(this);
-  itemsTable->setColumnCount(3);
-  itemsTable->setHorizontalHeaderLabels({"Food", "Grams", "Calories"});
-  itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
-  layout->addWidget(itemsTable);
-
-  // Controls
-  clearButton = new QPushButton("Clear Meal", this);
-  connect(clearButton, &QPushButton::clicked, this, &MealWidget::clearMeal);
-  layout->addWidget(clearButton);
-
-  // Totals
-  layout->addWidget(new QLabel("Total Nutrition", this));
-  totalsTable = new QTableWidget(this);
-  totalsTable->setColumnCount(3);
-  totalsTable->setHorizontalHeaderLabels({"Nutrient", "Total", "Unit"});
-  totalsTable->horizontalHeader()->setSectionResizeMode(0,
-                                                        QHeaderView::Stretch);
-  layout->addWidget(totalsTable);
+MealWidget::MealWidget(QWidget* parent) : QWidget(parent) {
+    auto* layout = new QVBoxLayout(this);
+
+    // Items List
+    layout->addWidget(new QLabel("Meal Composition", this));
+    itemsTable = new QTableWidget(this);
+    itemsTable->setColumnCount(3);
+    itemsTable->setHorizontalHeaderLabels({"Food", "Grams", "Calories"});
+    itemsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+    layout->addWidget(itemsTable);
+
+    // Controls
+    clearButton = new QPushButton("Clear Meal", this);
+    connect(clearButton, &QPushButton::clicked, this, &MealWidget::clearMeal);
+    layout->addWidget(clearButton);
+
+    // Totals
+    layout->addWidget(new QLabel("Total Nutrition", this));
+    totalsTable = new QTableWidget(this);
+    totalsTable->setColumnCount(3);
+    totalsTable->setHorizontalHeaderLabels({"Nutrient", "Total", "Unit"});
+    totalsTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
+    layout->addWidget(totalsTable);
+
+    refresh();  // Load initial state
+}
+
+void MealWidget::addFood(int foodId, const QString& foodName, double grams) {
+    // Default to meal_id 1 (e.g. Breakfast/General) for now
+    // TODO: Add UI to select meal
+    m_mealRepo.addFoodLog(foodId, grams, 1);
+    refresh();
 }
 
-void MealWidget::addFood(int foodId, const QString &foodName, double grams) {
-  std::vector<Nutrient> baseNutrients = repository.getFoodNutrients(foodId);
-
-  MealItem item;
-  item.foodId = foodId;
-  item.name = foodName;
-  item.grams = grams;
-  item.nutrients_100g = baseNutrients;
-
-  mealItems.push_back(item);
-
-  // Update Items Table
-  int row = itemsTable->rowCount();
-  itemsTable->insertRow(row);
-  itemsTable->setItem(row, 0, new QTableWidgetItem(foodName));
-  itemsTable->setItem(row, 1, new QTableWidgetItem(QString::number(grams)));
-
-  // Calculate Calories (ID 208 usually, or find by name?)
-  // repository returns IDs based on DB. 208 is KCAL in SR28.
-  double kcal = 0;
-  for (const auto &nut : baseNutrients) {
-    if (nut.id == 208) {
-      kcal = (nut.amount * grams) / 100.0;
-      break;
+void MealWidget::refresh() {
+    mealItems.clear();
+    itemsTable->setRowCount(0);
+
+    auto logs = m_mealRepo.getDailyLogs();  // defaults to today
+
+    for (const auto& log : logs) {
+        std::vector<Nutrient> baseNutrients = repository.getFoodNutrients(log.foodId);
+
+        MealItem item;
+        item.foodId = log.foodId;
+        item.name = log.foodName;
+        item.grams = log.grams;
+        item.nutrients_100g = baseNutrients;
+        mealItems.push_back(item);
+
+        // Update Items Table
+        int row = itemsTable->rowCount();
+        itemsTable->insertRow(row);
+        itemsTable->setItem(row, 0, new QTableWidgetItem(item.name));
+        itemsTable->setItem(row, 1, new QTableWidgetItem(QString::number(item.grams)));
+
+        // Calculate Calories (ID 208)
+        double kcal = 0;
+        for (const auto& nut : baseNutrients) {
+            if (nut.id == 208) {
+                kcal = (nut.amount * item.grams) / 100.0;
+                break;
+            }
+        }
+        itemsTable->setItem(row, 2, new QTableWidgetItem(QString::number(kcal, 'f', 1)));
     }
-  }
-  itemsTable->setItem(row, 2,
-                      new QTableWidgetItem(QString::number(kcal, 'f', 1)));
 
-  updateTotals();
+    updateTotals();
 }
 
 void MealWidget::clearMeal() {
-  mealItems.clear();
-  itemsTable->setRowCount(0);
-  updateTotals();
+    m_mealRepo.clearDailyLogs();
+    refresh();
 }
 
 void MealWidget::updateTotals() {
-  std::map<int, double> totals; // id -> amount
-  std::map<int, QString> units;
-  std::map<int, QString> names;
-
-  for (const auto &item : mealItems) {
-    double scale = item.grams / 100.0;
-    for (const auto &nut : item.nutrients_100g) {
-      totals[nut.id] += nut.amount * scale;
-      names.try_emplace(nut.id, nut.description);
-      units.try_emplace(nut.id, nut.unit);
+    std::map<int, double> totals;  // id -> amount
+    std::map<int, QString> units;
+    std::map<int, QString> names;
+
+    for (const auto& item : mealItems) {
+        double scale = item.grams / 100.0;
+        for (const auto& nut : item.nutrients_100g) {
+            totals[nut.id] += nut.amount * scale;
+            names.try_emplace(nut.id, nut.description);
+            units.try_emplace(nut.id, nut.unit);
+        }
+    }
+
+    totalsTable->setRowCount(static_cast<int>(totals.size()));
+    int row = 0;
+    for (const auto& pair : totals) {
+        int nid = pair.first;
+        double amount = pair.second;
+
+        totalsTable->setItem(row, 0, new QTableWidgetItem(names[nid]));
+        totalsTable->setItem(row, 1, new QTableWidgetItem(QString::number(amount, 'f', 2)));
+        totalsTable->setItem(row, 2, new QTableWidgetItem(units[nid]));
+        row++;
     }
-  }
-
-  totalsTable->setRowCount(static_cast<int>(totals.size()));
-  int row = 0;
-  for (const auto &pair : totals) {
-    int nid = pair.first;
-    double amount = pair.second;
-
-    totalsTable->setItem(row, 0, new QTableWidgetItem(names[nid]));
-    totalsTable->setItem(row, 1,
-                         new QTableWidgetItem(QString::number(amount, 'f', 2)));
-    totalsTable->setItem(row, 2, new QTableWidgetItem(units[nid]));
-    row++;
-  }
 }
index 877fff383a677b6798cb8827f9d3b6bf621d6ec8..251ebac2260fa5087e4b0e94ff47b7185146f7f5 100644 (file)
@@ -1,84 +1,81 @@
 #include "widgets/rdasettingswidget.h"
-#include "db/databasemanager.h"
+
 #include <QHeaderView>
 #include <QLabel>
 #include <QSqlQuery>
 #include <QVBoxLayout>
 
-RDASettingsWidget::RDASettingsWidget(FoodRepository &repository,
-                                     QWidget *parent)
+#include "db/databasemanager.h"
+
+RDASettingsWidget::RDASettingsWidget(FoodRepository& repository, QWidget* parent)
     : QDialog(parent), m_repository(repository) {
-  setWindowTitle("RDA Settings");
-  resize(600, 400);
+    setWindowTitle("RDA Settings");
+    resize(600, 400);
 
-  auto *layout = new QVBoxLayout(this);
-  layout->addWidget(new QLabel("Customize your Recommended Daily Allowances "
-                               "(RDA). Changes are saved automatically."));
+    auto* layout = new QVBoxLayout(this);
+    layout->addWidget(
+        new QLabel("Customize your Recommended Daily Allowances "
+                   "(RDA). Changes are saved automatically."));
 
-  m_table = new QTableWidget(this);
-  m_table->setColumnCount(4);
-  m_table->setHorizontalHeaderLabels({"ID", "Nutrient", "RDA", "Unit"});
-  m_table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
-  m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
+    m_table = new QTableWidget(this);
+    m_table->setColumnCount(4);
+    m_table->setHorizontalHeaderLabels({"ID", "Nutrient", "RDA", "Unit"});
+    m_table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+    m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
 
-  loadData();
+    loadData();
 
-  connect(m_table, &QTableWidget::cellChanged, this,
-          &RDASettingsWidget::onCellChanged);
+    connect(m_table, &QTableWidget::cellChanged, this, &RDASettingsWidget::onCellChanged);
 
-  layout->addWidget(m_table);
+    layout->addWidget(m_table);
 }
 
 void RDASettingsWidget::loadData() {
-  m_loading = true;
-  m_table->setRowCount(0);
+    m_loading = true;
+    m_table->setRowCount(0);
 
-  QSqlDatabase db = DatabaseManager::instance().database();
-  if (!db.isOpen())
-    return;
+    QSqlDatabase db = DatabaseManager::instance().database();
+    if (!db.isOpen()) return;
 
-  // Get metadata from USDA
-  QSqlQuery query(
-      "SELECT id, nutr_desc, unit FROM nutrients_overview ORDER BY nutr_desc",
-      db);
-  auto currentRdas = m_repository.getNutrientRdas();
+    // Get metadata from USDA
+    QSqlQuery query("SELECT id, nutr_desc, unit FROM nutrients_overview ORDER BY nutr_desc", db);
+    auto currentRdas = m_repository.getNutrientRdas();
 
-  int row = 0;
-  while (query.next()) {
-    int id = query.value(0).toInt();
-    QString name = query.value(1).toString();
-    QString unit = query.value(2).toString();
-    double rda = currentRdas.count(id) != 0U ? currentRdas[id] : 0.0;
+    int row = 0;
+    while (query.next()) {
+        int id = query.value(0).toInt();
+        QString name = query.value(1).toString();
+        QString unit = query.value(2).toString();
+        double rda = currentRdas.count(id) != 0U ? currentRdas[id] : 0.0;
 
-    m_table->insertRow(row);
+        m_table->insertRow(row);
 
-    auto *idItem = new QTableWidgetItem(QString::number(id));
-    idItem->setFlags(idItem->flags() & ~Qt::ItemIsEditable);
-    m_table->setItem(row, 0, idItem);
+        auto* idItem = new QTableWidgetItem(QString::number(id));
+        idItem->setFlags(idItem->flags() & ~Qt::ItemIsEditable);
+        m_table->setItem(row, 0, idItem);
 
-    auto *nameItem = new QTableWidgetItem(name);
-    nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
-    m_table->setItem(row, 1, nameItem);
+        auto* nameItem = new QTableWidgetItem(name);
+        nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable);
+        m_table->setItem(row, 1, nameItem);
 
-    auto *rdaItem = new QTableWidgetItem(QString::number(rda));
-    m_table->setItem(row, 2, rdaItem);
+        auto* rdaItem = new QTableWidgetItem(QString::number(rda));
+        m_table->setItem(row, 2, rdaItem);
 
-    auto *unitItem = new QTableWidgetItem(unit);
-    unitItem->setFlags(unitItem->flags() & ~Qt::ItemIsEditable);
-    m_table->setItem(row, 3, unitItem);
+        auto* unitItem = new QTableWidgetItem(unit);
+        unitItem->setFlags(unitItem->flags() & ~Qt::ItemIsEditable);
+        m_table->setItem(row, 3, unitItem);
 
-    row++;
-  }
+        row++;
+    }
 
-  m_loading = false;
+    m_loading = false;
 }
 
 void RDASettingsWidget::onCellChanged(int row, int column) {
-  if (m_loading || column != 2)
-    return;
+    if (m_loading || column != 2) return;
 
-  int id = m_table->item(row, 0)->text().toInt();
-  double value = m_table->item(row, 2)->text().toDouble();
+    int id = m_table->item(row, 0)->text().toInt();
+    double value = m_table->item(row, 2)->text().toDouble();
 
-  m_repository.updateRda(id, value);
+    m_repository.updateRda(id, value);
 }
index 37193c66a78ae5c57c89537fedc390761999872c..81683106fd34bd15234f277e343fd89c9deac541 100644 (file)
@@ -1,5 +1,5 @@
 #include "widgets/searchwidget.h"
-#include "widgets/weightinputdialog.h"
+
 #include <QAction>
 #include <QHBoxLayout>
 #include <QHeaderView>
 #include <QMessageBox>
 #include <QVBoxLayout>
 
-SearchWidget::SearchWidget(QWidget *parent) : QWidget(parent) {
-  auto *layout = new QVBoxLayout(this);
-
-  // Search bar
-  auto *searchLayout = new QHBoxLayout();
-  searchInput = new QLineEdit(this);
-  searchInput->setPlaceholderText("Search for food...");
-
-  searchTimer = new QTimer(this);
-  searchTimer->setSingleShot(true);
-  searchTimer->setInterval(600); // 600ms debounce
-
-  connect(searchInput, &QLineEdit::textChanged, this,
-          [=]() { searchTimer->start(); });
-  connect(searchTimer, &QTimer::timeout, this, &SearchWidget::performSearch);
-  connect(searchInput, &QLineEdit::returnPressed, this,
-          &SearchWidget::performSearch);
-
-  searchButton = new QPushButton("Search", this);
-  connect(searchButton, &QPushButton::clicked, this,
-          &SearchWidget::performSearch);
-
-  searchLayout->addWidget(searchInput);
-  searchLayout->addWidget(searchButton);
-  layout->addLayout(searchLayout);
-
-  // Results table
-  resultsTable = new QTableWidget(this);
-  resultsTable->setColumnCount(7);
-  resultsTable->setHorizontalHeaderLabels(
-      {"ID", "Description", "Group", "Nutr", "Amino", "Flav", "Score"});
-
-  resultsTable->horizontalHeader()->setSectionResizeMode(1,
-                                                         QHeaderView::Stretch);
-  resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
-  resultsTable->setSelectionMode(QAbstractItemView::SingleSelection);
-  resultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
-  resultsTable->setContextMenuPolicy(Qt::CustomContextMenu);
-
-  connect(resultsTable, &QTableWidget::cellDoubleClicked, this,
-          &SearchWidget::onRowDoubleClicked);
-  connect(resultsTable, &QTableWidget::customContextMenuRequested, this,
-          &SearchWidget::onCustomContextMenu);
-
-  layout->addWidget(resultsTable);
+#include "widgets/weightinputdialog.h"
+
+SearchWidget::SearchWidget(QWidget* parent) : QWidget(parent) {
+    auto* layout = new QVBoxLayout(this);
+
+    // Search bar
+    auto* searchLayout = new QHBoxLayout();
+    searchInput = new QLineEdit(this);
+    searchInput->setPlaceholderText("Search for food...");
+
+    searchTimer = new QTimer(this);
+    searchTimer->setSingleShot(true);
+    searchTimer->setInterval(600);  // 600ms debounce
+
+    connect(searchInput, &QLineEdit::textChanged, this, [=]() { searchTimer->start(); });
+    connect(searchTimer, &QTimer::timeout, this, &SearchWidget::performSearch);
+    connect(searchInput, &QLineEdit::returnPressed, this, &SearchWidget::performSearch);
+
+    searchButton = new QPushButton("Search", this);
+    connect(searchButton, &QPushButton::clicked, this, &SearchWidget::performSearch);
+
+    searchLayout->addWidget(searchInput);
+    searchLayout->addWidget(searchButton);
+    layout->addLayout(searchLayout);
+
+    // Results table
+    resultsTable = new QTableWidget(this);
+    resultsTable->setColumnCount(7);
+    resultsTable->setHorizontalHeaderLabels(
+        {"ID", "Description", "Group", "Nutr", "Amino", "Flav", "Score"});
+
+    resultsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+    resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
+    resultsTable->setSelectionMode(QAbstractItemView::SingleSelection);
+    resultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
+    resultsTable->setContextMenuPolicy(Qt::CustomContextMenu);
+
+    connect(resultsTable, &QTableWidget::cellDoubleClicked, this,
+            &SearchWidget::onRowDoubleClicked);
+    connect(resultsTable, &QTableWidget::customContextMenuRequested, this,
+            &SearchWidget::onCustomContextMenu);
+
+    layout->addWidget(resultsTable);
 }
 
 void SearchWidget::performSearch() {
-  QString query = searchInput->text().trimmed();
-  if (query.length() < 2)
-    return;
-
-  resultsTable->setRowCount(0);
-
-  std::vector<FoodItem> results = repository.searchFoods(query);
-
-  resultsTable->setRowCount(static_cast<int>(results.size()));
-  for (int i = 0; i < static_cast<int>(results.size()); ++i) {
-    const auto &item = results[i];
-    resultsTable->setItem(i, 0, new QTableWidgetItem(QString::number(item.id)));
-    resultsTable->setItem(i, 1, new QTableWidgetItem(item.description));
-    resultsTable->setItem(i, 2, new QTableWidgetItem(item.foodGroupName));
-    resultsTable->setItem(
-        i, 3, new QTableWidgetItem(QString::number(item.nutrientCount)));
-    resultsTable->setItem(
-        i, 4, new QTableWidgetItem(QString::number(item.aminoCount)));
-    resultsTable->setItem(
-        i, 5, new QTableWidgetItem(QString::number(item.flavCount)));
-    resultsTable->setItem(i, 6,
-                          new QTableWidgetItem(QString::number(item.score)));
-  }
+    QString query = searchInput->text().trimmed();
+    if (query.length() < 2) return;
+
+    resultsTable->setRowCount(0);
+
+    std::vector<FoodItem> results = repository.searchFoods(query);
+
+    resultsTable->setRowCount(static_cast<int>(results.size()));
+    for (int i = 0; i < static_cast<int>(results.size()); ++i) {
+        const auto& item = results[i];
+        resultsTable->setItem(i, 0, new QTableWidgetItem(QString::number(item.id)));
+        resultsTable->setItem(i, 1, new QTableWidgetItem(item.description));
+        resultsTable->setItem(i, 2, new QTableWidgetItem(item.foodGroupName));
+        resultsTable->setItem(i, 3, new QTableWidgetItem(QString::number(item.nutrientCount)));
+        resultsTable->setItem(i, 4, new QTableWidgetItem(QString::number(item.aminoCount)));
+        resultsTable->setItem(i, 5, new QTableWidgetItem(QString::number(item.flavCount)));
+        resultsTable->setItem(i, 6, new QTableWidgetItem(QString::number(item.score)));
+    }
 }
 
 void SearchWidget::onRowDoubleClicked(int row, int column) {
-  Q_UNUSED(column);
-  QTableWidgetItem *idItem = resultsTable->item(row, 0);
-  QTableWidgetItem *descItem = resultsTable->item(row, 1);
+    Q_UNUSED(column);
+    QTableWidgetItem* idItem = resultsTable->item(row, 0);
+    QTableWidgetItem* descItem = resultsTable->item(row, 1);
 
-  if (idItem != nullptr && descItem != nullptr) {
-    emit foodSelected(idItem->text().toInt(), descItem->text());
-  }
+    if (idItem != nullptr && descItem != nullptr) {
+        emit foodSelected(idItem->text().toInt(), descItem->text());
+    }
 }
 
-void SearchWidget::onCustomContextMenu(const QPoint &pos) {
-  QTableWidgetItem *item = resultsTable->itemAt(pos);
-  if (item == nullptr)
-    return;
+void SearchWidget::onCustomContextMenu(const QPoint& pos) {
+    QTableWidgetItem* item = resultsTable->itemAt(pos);
+    if (item == nullptr) return;
 
-  int row = item->row();
-  QTableWidgetItem *idItem = resultsTable->item(row, 0);
-  QTableWidgetItem *descItem = resultsTable->item(row, 1);
+    int row = item->row();
+    QTableWidgetItem* idItem = resultsTable->item(row, 0);
+    QTableWidgetItem* descItem = resultsTable->item(row, 1);
 
-  if (idItem == nullptr || descItem == nullptr)
-    return;
+    if (idItem == nullptr || descItem == nullptr) return;
 
-  int foodId = idItem->text().toInt();
-  QString foodName = descItem->text();
+    int foodId = idItem->text().toInt();
+    QString foodName = descItem->text();
 
-  QMenu menu(this);
-  QAction *analyzeAction = menu.addAction("Analyze");
-  QAction *addToMealAction = menu.addAction("Add to Meal");
+    QMenu menu(this);
+    QAction* analyzeAction = menu.addAction("Analyze");
+    QAction* addToMealAction = menu.addAction("Add to Meal");
 
-  QAction *selectedAction =
-      menu.exec(resultsTable->viewport()->mapToGlobal(pos));
+    QAction* selectedAction = menu.exec(resultsTable->viewport()->mapToGlobal(pos));
 
-  if (selectedAction == analyzeAction) {
-    emit foodSelected(foodId, foodName);
-  } else if (selectedAction == addToMealAction) {
-    std::vector<ServingWeight> servings = repository.getFoodServings(foodId);
-    WeightInputDialog dlg(foodName, servings, this);
-    if (dlg.exec() == QDialog::Accepted) {
-      emit addToMealRequested(foodId, foodName, dlg.getGrams());
+    if (selectedAction == analyzeAction) {
+        emit foodSelected(foodId, foodName);
+    } else if (selectedAction == addToMealAction) {
+        std::vector<ServingWeight> servings = repository.getFoodServings(foodId);
+        WeightInputDialog dlg(foodName, servings, this);
+        if (dlg.exec() == QDialog::Accepted) {
+            emit addToMealRequested(foodId, foodName, dlg.getGrams());
+        }
     }
-  }
 }
index b5e2b5d179417cdf1a3b36cec75ab31c3a70e53f..d3e9127a5c9f860d03c600f0a9c659e870b95f9b 100644 (file)
@@ -1,63 +1,62 @@
 #include "widgets/weightinputdialog.h"
+
 #include <QHBoxLayout>
 #include <QPushButton>
 #include <QVBoxLayout>
 
-WeightInputDialog::WeightInputDialog(const QString &foodName,
-                                     const std::vector<ServingWeight> &servings,
-                                     QWidget *parent)
+WeightInputDialog::WeightInputDialog(const QString& foodName,
+                                     const std::vector<ServingWeight>& servings, QWidget* parent)
     : QDialog(parent), m_servings(servings) {
-  setWindowTitle("Add to Meal - " + foodName);
-  auto *layout = new QVBoxLayout(this);
-
-  layout->addWidget(
-      new QLabel("How much " + foodName + " are you adding?", this));
-
-  auto *inputLayout = new QHBoxLayout();
-  amountSpinBox = new QDoubleSpinBox(this);
-  amountSpinBox->setRange(0.1, 10000.0);
-  amountSpinBox->setValue(1.0);
-  amountSpinBox->setDecimals(2);
-
-  unitComboBox = new QComboBox(this);
-  unitComboBox->addItem("Grams (g)", 1.0);
-  unitComboBox->addItem("Ounces (oz)", GRAMS_PER_OZ);
-  unitComboBox->addItem("Pounds (lb)", GRAMS_PER_LB);
-
-  for (const auto &sw : servings) {
-    unitComboBox->addItem(sw.description, sw.grams);
-  }
-
-  // Default to Grams and set value to 100 if Grams is selected
-  unitComboBox->setCurrentIndex(0);
-  amountSpinBox->setValue(100.0);
-
-  // Update value when unit changes? No, let's keep it simple.
-  // Usually 100g is a good default, but 1 serving might be better if available.
-  if (!servings.empty()) {
-    unitComboBox->setCurrentIndex(3); // First serving
-    amountSpinBox->setValue(1.0);
-  }
-
-  inputLayout->addWidget(amountSpinBox);
-  inputLayout->addWidget(unitComboBox);
-  layout->addLayout(inputLayout);
+    setWindowTitle("Add to Meal - " + foodName);
+    auto* layout = new QVBoxLayout(this);
 
-  auto *buttonLayout = new QHBoxLayout();
-  auto *okButton = new QPushButton("Add to Meal", this);
-  auto *cancelButton = new QPushButton("Cancel", this);
+    layout->addWidget(new QLabel("How much " + foodName + " are you adding?", this));
 
-  connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
-  connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
-
-  buttonLayout->addStretch();
-  buttonLayout->addWidget(cancelButton);
-  buttonLayout->addWidget(okButton);
-  layout->addLayout(buttonLayout);
+    auto* inputLayout = new QHBoxLayout();
+    amountSpinBox = new QDoubleSpinBox(this);
+    amountSpinBox->setRange(0.1, 10000.0);
+    amountSpinBox->setValue(1.0);
+    amountSpinBox->setDecimals(2);
+
+    unitComboBox = new QComboBox(this);
+    unitComboBox->addItem("Grams (g)", 1.0);
+    unitComboBox->addItem("Ounces (oz)", GRAMS_PER_OZ);
+    unitComboBox->addItem("Pounds (lb)", GRAMS_PER_LB);
+
+    for (const auto& sw : servings) {
+        unitComboBox->addItem(sw.description, sw.grams);
+    }
+
+    // Default to Grams and set value to 100 if Grams is selected
+    unitComboBox->setCurrentIndex(0);
+    amountSpinBox->setValue(100.0);
+
+    // Update value when unit changes? No, let's keep it simple.
+    // Usually 100g is a good default, but 1 serving might be better if available.
+    if (!servings.empty()) {
+        unitComboBox->setCurrentIndex(3);  // First serving
+        amountSpinBox->setValue(1.0);
+    }
+
+    inputLayout->addWidget(amountSpinBox);
+    inputLayout->addWidget(unitComboBox);
+    layout->addLayout(inputLayout);
+
+    auto* buttonLayout = new QHBoxLayout();
+    auto* okButton = new QPushButton("Add to Meal", this);
+    auto* cancelButton = new QPushButton("Cancel", this);
+
+    connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
+    connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
+
+    buttonLayout->addStretch();
+    buttonLayout->addWidget(cancelButton);
+    buttonLayout->addWidget(okButton);
+    layout->addLayout(buttonLayout);
 }
 
 double WeightInputDialog::getGrams() const {
-  double amount = amountSpinBox->value();
-  double multiplier = unitComboBox->currentData().toDouble();
-  return amount * multiplier;
+    double amount = amountSpinBox->value();
+    double multiplier = unitComboBox->currentData().toDouble();
+    return amount * multiplier;
 }
index 60a11c20910cdd22fff27091aff1317ccef98b47..c45d3ae9ca7793120c0720587e82f34a684a2994 100644 (file)
@@ -1,57 +1,56 @@
-#include "db/databasemanager.h"
-#include "db/foodrepository.h"
 #include <QDir>
 #include <QFileInfo>
 #include <QtTest>
 
+#include "db/databasemanager.h"
+#include "db/foodrepository.h"
+
 class TestFoodRepository : public QObject {
-  Q_OBJECT
+    Q_OBJECT
 
 private slots:
-  void initTestCase() {
-    // Setup DB connection
-    // Allow override via environment variable for CI/testing
-    QString envPath = qgetenv("NUTRA_DB_PATH");
-    QString dbPath =
-        envPath.isEmpty() ? QDir::homePath() + "/.nutra/usda.sqlite3" : envPath;
-
-    if (!QFileInfo::exists(dbPath)) {
-      QSKIP("Database file not found (NUTRA_DB_PATH or ~/.nutra/usda.sqlite3). "
-            "Skipping DB tests.");
+    void initTestCase() {
+        // Setup DB connection
+        // Allow override via environment variable for CI/testing
+        QString envPath = qgetenv("NUTRA_DB_PATH");
+        QString dbPath = envPath.isEmpty() ? QDir::homePath() + "/.nutra/usda.sqlite3" : envPath;
+
+        if (!QFileInfo::exists(dbPath)) {
+            QSKIP(
+                "Database file not found (NUTRA_DB_PATH or ~/.nutra/usda.sqlite3). "
+                "Skipping DB tests.");
+        }
+
+        bool connected = DatabaseManager::instance().connect(dbPath);
+        QVERIFY2(connected, "Failed to connect to database");
+    }
+
+    void testSearchFoods() {
+        FoodRepository repo;
+        auto results = repo.searchFoods("apple");
+        QVERIFY2(!results.empty(), "Search should return results for 'apple'");
+        bool found = false;
+        for (const auto& item : results) {
+            if (item.description.contains("Apple", Qt::CaseInsensitive)) {
+                found = true;
+                break;
+            }
+        }
+        QVERIFY2(found, "Search results should contain 'Apple'");
     }
 
-    bool connected = DatabaseManager::instance().connect(dbPath);
-    QVERIFY2(connected, "Failed to connect to database");
-  }
-
-  void testSearchFoods() {
-    FoodRepository repo;
-    auto results = repo.searchFoods("apple");
-    QVERIFY2(!results.empty(), "Search should return results for 'apple'");
-    bool found = false;
-    for (const auto &item : results) {
-      if (item.description.contains("Apple", Qt::CaseInsensitive)) {
-        found = true;
-        break;
-      }
+    void testGetFoodNutrients() {
+        FoodRepository repo;
+        // Known ID for "Apples, raw, with skin" might be 9003 in SR28, but let's
+        // search first or pick a known one if we knew it. Let's just use the first
+        // result from search.
+        auto results = repo.searchFoods("apple");
+        if (results.empty()) QSKIP("No foods found to test nutrients");
+
+        int foodId = results[0].id;
+        auto nutrients = repo.getFoodNutrients(foodId);
+        QVERIFY2(!nutrients.empty(), "Nutrients should not be empty for a valid food");
     }
-    QVERIFY2(found, "Search results should contain 'Apple'");
-  }
-
-  void testGetFoodNutrients() {
-    FoodRepository repo;
-    // Known ID for "Apples, raw, with skin" might be 9003 in SR28, but let's
-    // search first or pick a known one if we knew it. Let's just use the first
-    // result from search.
-    auto results = repo.searchFoods("apple");
-    if (results.empty())
-      QSKIP("No foods found to test nutrients");
-
-    int foodId = results[0].id;
-    auto nutrients = repo.getFoodNutrients(foodId);
-    QVERIFY2(!nutrients.empty(),
-             "Nutrients should not be empty for a valid food");
-  }
 };
 
 QTEST_MAIN(TestFoodRepository)