diff --git a/assets/js/main.js b/assets/js/main.js index bc96fdc..01b8c03 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -41,4 +41,79 @@ document.addEventListener('DOMContentLoaded', function () { ].join(''); alertContainer.append(wrapper); } + + // --- Search Functionality --- + const searchForm = document.getElementById('searchForm'); + if (searchForm) { + const searchInput = document.getElementById('searchInput'); + const searchButton = document.getElementById('searchButton'); + const searchResultsSection = document.getElementById('searchResults'); + const resultsContainer = document.getElementById('resultsContainer'); + const noResults = document.getElementById('noResults'); + + searchForm.addEventListener('submit', function(e) { + e.preventDefault(); + const query = searchInput.value.trim(); + + if (query === '') { + return; + } + + // Show loading state + searchButton.disabled = true; + searchButton.innerHTML = ' Searching...'; + resultsContainer.innerHTML = ''; + noResults.style.display = 'none'; + searchResultsSection.style.display = 'block'; + + fetch(`search.php?q=${encodeURIComponent(query)}`) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + if (data.length > 0) { + displayResults(data); + } else { + noResults.style.display = 'block'; + } + }) + .catch(error => { + console.error('Search error:', error); + resultsContainer.innerHTML = '

An error occurred while searching. Please try again.

'; + }) + .finally(() => { + // Restore button state + searchButton.disabled = false; + searchButton.innerHTML = 'Search'; + }); + }); + + function displayResults(products) { + products.forEach(product => { + const pricesHtml = product.prices.map(p => ` +
  • + ${p.retailer} + R ${parseFloat(p.price).toFixed(2)} +
  • + `).join(''); + + const productCard = ` +
    +
    +
    +
    ${product.name}
    +
      + ${pricesHtml} +
    +
    +
    +
    + `; + resultsContainer.innerHTML += productCard; + }); + } + } }); diff --git a/db/migrate.php b/db/migrate.php new file mode 100644 index 0000000..5d666da --- /dev/null +++ b/db/migrate.php @@ -0,0 +1,70 @@ +exec($sql); + echo "Success.\n"; + } catch (PDOException $e) { + echo "Error running migration " . basename($file) . ": " . $e->getMessage() . "\n"; + } + } +} + +function seed_data(PDO $pdo): void +{ + $products = [ + ['name' => 'Milk 1L', 'retailer' => 'Pick n Pay', 'price' => 25.99], + ['name' => 'Milk 1L', 'retailer' => 'Checkers', 'price' => 24.99], + ['name' => 'Bread (White)', 'retailer' => 'Pick n Pay', 'price' => 15.50], + ['name' => 'Bread (White)', 'retailer' => 'Checkers', 'price' => 14.99], + ['name' => 'Eggs (12 pack)', 'retailer' => 'Pick n Pay', 'price' => 32.00], + ['name' => 'Eggs (12 pack)', 'retailer' => 'Checkers', 'price' => 31.50], + ['name' => 'Cheese (250g)', 'retailer' => 'Pick n Pay', 'price' => 55.00], + ['name' => 'Cheese (250g)', 'retailer' => 'Checkers', 'price' => 52.99], + ]; + + $stmt = $pdo->prepare( + 'INSERT INTO products (name, retailer, price) VALUES (:name, :retailer, :price) + ON DUPLICATE KEY UPDATE price = VALUES(price)' + ); + + echo "\nSeeding data...\n"; + foreach ($products as $product) { + try { + $stmt->execute($product); + echo "Seeded/Updated: " . $product['name'] . " (" . $product['retailer'] . ")\n"; + } catch (PDOException $e) { + echo "Error seeding " . $product['name'] . ": " . $e->getMessage() . "\n"; + } + } + echo "Seeding complete.\n"; +} + +try { + $pdo = db(); + run_migrations($pdo); + seed_data($pdo); + echo "\nDatabase setup complete!\n"; +} catch (PDOException $e) { + die("Database connection failed: " . $e->getMessage() . "\n"); +} diff --git a/db/migrations/001_create_products_table.sql b/db/migrations/001_create_products_table.sql new file mode 100644 index 0000000..70a89ba --- /dev/null +++ b/db/migrations/001_create_products_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS products ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + retailer VARCHAR(255) NOT NULL, + price DECIMAL(10, 2) NOT NULL, + last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY `product_retailer` (`name`,`retailer`) +); diff --git a/index.php b/index.php index c1093ee..bb0a079 100644 --- a/index.php +++ b/index.php @@ -5,12 +5,28 @@

    PricePal SA

    Your smart shopping companion for comparing prices across South African retailers.

    - - Download on Google Play - +
    +
    + + +
    +
    + + +
    diff --git a/search.php b/search.php new file mode 100644 index 0000000..192c9cb --- /dev/null +++ b/search.php @@ -0,0 +1,45 @@ +prepare( + 'SELECT name, retailer, price FROM products WHERE name LIKE :query ORDER BY name, price' + ); + + $stmt->execute(['query' => '%' . $query . '%']); + $results = $stmt->fetchAll(); + + // Group prices by product name + $products = []; + foreach ($results as $row) { + if (!isset($products[$row['name']])) { + $products[$row['name']] = [ + 'name' => $row['name'], + 'prices' => [] + ]; + } + $products[$row['name']]['prices'][] = [ + 'retailer' => $row['retailer'], + 'price' => $row['price'] + ]; + } + + // Return as a simple array of products + echo json_encode(array_values($products)); + +} catch (PDOException $e) { + http_response_code(500); + echo json_encode(['error' => 'Database error: ' . $e->getMessage()]); +}