Webbserverprogrammering 1

Show sourcecode

The following files exists in this folder. Click to view.

webbserverprogrammering/submissions/projekt-matkort-handler/

.github/
add_logs.php
admin/
api/
card_balance.php
classes/
config/
food_logs.php
forgot_password.php
includes/
index.php
insert_restaurants.php
install.php
login.php
logout.php
public/
register.php
reset_password.php
verify.php

food_logs.php

370 lines UTF-8 Windows (CRLF)
<?php
ini_set
('display_errors'1);
ini_set('display_startup_errors'1);
error_reporting(E_ALL);
session_start();

include_once 
'./config/database.php';
include_once 
'./classes/Restaurant.php';
include_once 
'./classes/FoodLog.php';

if (!isset(
$_SESSION['user_id'])) {
    
header("Location: login.php");
    exit;
}

$restaurantModel = new Restaurant($pdo);
$foodLogModel = new FoodLogs($pdo);
$error '';

// AJAX restaurant search using Restaurant class
if (isset($_REQUEST["get_favorites"])) {
    try {
        
$term $_REQUEST["term"] ?? '';
        
$user_id $_SESSION['user_id'];
        
$favorites $restaurantModel->getUserFavorites($user_id);
        
$fav_ids array_column($favorites'id');

        if (empty(
$term)) {
            
// Show only favorites if term is empty
            
if ($favorites) {
                foreach (
$favorites as $row) {
                    echo 
"<p data-id='" htmlspecialchars($row['id']) . "' style='display:flex; justify-content:space-between; align-items:center;'><span class='restaurant-name'>" htmlspecialchars($row['name']) . "</span><span style='cursor:pointer; display:flex; align-items:center;' onclick='event.stopPropagation(); toggleBookmark(" htmlspecialchars($row['id']) . ", this)'><i data-feather='bookmark' class='bookmark-icon booked' style='width:16px; height:16px;'></i></span></p>";
                }
            } else {
                echo 
"<p style='text-align:center; color:#777; font-style:italic;'>Sök efter en restaurang...</p>";
            }
        } else {
            
// Show search results
            
$results $restaurantModel->searchByName($term);
            if (
$results) {
                foreach (
$results as $row) {
                    
$is_fav in_array($row['id'], $fav_ids);
                    
$fillClass $is_fav "booked" "unbooked";
                    echo 
"<p data-id='" htmlspecialchars($row['id']) . "' style='display:flex; justify-content:space-between; align-items:center;'><span class='restaurant-name'>" htmlspecialchars($row['restaurant_name']) . "</span><span style='cursor:pointer; display:flex; align-items:center;' onclick='event.stopPropagation(); toggleBookmark(" htmlspecialchars($row['id']) . ", this)'><i data-feather='bookmark' class='bookmark-icon " $fillClass "' style='width:16px; height:16px;'></i></span></p>";
                }
            } else {
                echo 
"<p style='text-align:center; color:#777; font-style:italic;'>Inga resultat hittades</p>";
            }
        }
        
// Re-init feather icons for the new DOM elements since the output is echoed to JS
        
echo "<script>if(typeof feather !== 'undefined'){ feather.replace(); }</script>";
    } catch (
Exception $e) {
        echo 
"<p style='padding: 10px; margin: 0; color: red;'>Fel vid sökning: " htmlspecialchars($e->getMessage()) . "</p>";
    }
    exit;
}

if (
$_SERVER['REQUEST_METHOD'] === 'POST') {
  
$user_id $_SESSION['user_id'];
  
$restaurant_id $_POST['restaurant_id'];
  
$money_spent $_POST['money_spent'];
  
$healthy_rating $_POST['healthy_rating'];
  
$happy_rating $_POST['happy_rating'];
  
$comment $_POST['comment'];
  
$log_date = !empty($_POST['log_date']) ? $_POST['log_date'] : date('Y-m-d H:i:s');

  try {
    
$day_of_week date('N'strtotime($log_date));
    if (
$day_of_week >= 6) {
        throw new 
Exception("Du kan inte registrera matloggar under helger (lördag/söndag).");
    }
    if (
$money_spent 90) {
        throw new 
Exception("Du kan inte spendera mer än 90 kr.");
    }

    
$foodLogModel->create($user_id$restaurant_id$money_spent$healthy_rating$happy_rating$comment$log_date);
    
// Redirect to prevent form resubmission
    
header("Location: food_logs.php");
    exit;
  } catch (
Exception $e) {
    
$error $e->getMessage();
  }
}

$foodLogs $foodLogModel->getUserLogs($_SESSION["user_id"]);
$chartData $foodLogModel->getChartData($_SESSION["user_id"]);

$page_title 'Food Logs';
require_once 
'./includes/header.php';
?>

<div class="logs-container">
    <h1 style="text-align: center; margin-bottom: 20px;">Dina matloggar och statistik</h1>

    <!-- Tabs för vyval -->
    <div style="display: flex; justify-content: center; gap: 20px; margin-bottom: 30px;">
        <button id="btnStats" onclick="switchView('stats')" style="padding: 10px 25px; border-radius: var(--radius-md); border: none; background: var(--primary); color: var(--white); font-weight: bold; cursor: pointer; box-shadow: var(--shadow-soft);">Statistik</button>
        <button id="btnList" onclick="switchView('list')" style="padding: 10px 25px; border-radius: var(--radius-md); border: 1px solid var(--primary); background: transparent; color: var(--primary); font-weight: bold; cursor: pointer;">Historik</button>
    </div>

    <?php if ($error): ?>
      <div class="error-message"><?php echo htmlspecialchars($error); ?></div>
    <?php endif; ?>

    <div id="statsView" class="charts-wrapper" style="display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; margin-bottom: 40px;">
        <!-- Chart Controls -->
        <div style="width: 100%; text-align: center; margin-bottom: 15px; display: flex; justify-content: center; align-items: center; gap: 20px;">
            
            <button onclick="changeWeek(-1)" style="padding: 5px 15px; border: none; background: transparent; color: var(--primary); font-weight: bold; font-size: 1.1rem; cursor: pointer;">&#8592; Föregående</button>
            <span id="weekLabel" style="font-weight: bold; font-size: 1.2em; color: var(--text-dark);">Denna vecka</span>
            <button onclick="changeWeek(1)" style="padding: 5px 15px; border: none; background: transparent; color: var(--primary); font-weight: bold; font-size: 1.1rem; cursor: pointer;">Nästa &#8594;</button>
        </div>

        
        <div style="display: flex; gap: 20px; width: 100%; justify-content: space-evenly; max-width: 900px; flex-wrap: wrap;">
            <!-- Health Chart Container -->
            <div class="chart-container" style="flex: 1; min-width: 300px; padding: 20px; background: var(--white); border-radius: var(--radius-lg); box-shadow: var(--shadow-soft);">
                <div style="height: 250px;">
                    <canvas id="healthChart"></canvas>
                </div>
            </div>

            <!-- Happy Chart Container -->
            <div class="chart-container" style="flex: 1; min-width: 300px; padding: 20px; background: var(--white); border-radius: var(--radius-lg); box-shadow: var(--shadow-soft);">
                <div style="height: 250px;">
                    <canvas id="happyChart"></canvas>
                </div>
            </div>
        </div>
        
        <!-- Pie Chart Container -->
        <div class="chart-container" style="width: 100%; max-width: 400px; padding: 20px; background: var(--white); border-radius: var(--radius-lg); box-shadow: var(--shadow-soft); margin-top: 20px;">
            <canvas id="restaurantPieChart"></canvas>
        </div>
    </div>

    <!-- Feed / List of logs -->
    <div id="listView" class="logs-list" style="display: none; max-width: 800px; margin: 0 auto;">
        <h2>Senaste loggar</h2>
        <?php if($foodLogs && count($foodLogs) > 0): ?>
            <?php foreach($foodLogs as $log): ?>
                <div class="log-card" style="border: 1px solid var(--gray-light); background: var(--white); padding: 15px; margin-bottom: 10px; border-radius: var(--radius-sm); box-shadow: var(--shadow-soft);">
                    <h3><?php echo htmlspecialchars($log['restaurant_name']); ?></h3>
                    <p><strong>Datum:</strong> <?php echo htmlspecialchars(date('Y-m-d H:i'strtotime($log['log_date']))); ?></p>
                    <p><strong>Spenderat:</strong> <?php echo htmlspecialchars($log['money_spent']); ?> kr</p>
                    <p><strong>Hälsa:</strong> <?php echo htmlspecialchars($log['healthy_rating']); ?>/5 | <strong>Glädje:</strong> <?php echo htmlspecialchars($log['happy_rating']); ?>/3</p>
                    <?php if(!empty($log['comment'])): ?>
                        <p><em>"<?php echo htmlspecialchars($log['comment']); ?>"</em></p>
                    <?php endif; ?>
                </div>
            <?php endforeach; ?>
        <?php else: ?>
            <p>Du har inga sparade loggar ännu.</p>
        <?php endif; ?>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// --- Toggling between Chart Mode and List Mode ---
function switchView(mode) {
    const statsView = document.getElementById('statsView');
    const listView = document.getElementById('listView');
    const btnStats = document.getElementById('btnStats');
    const btnList = document.getElementById('btnList');

    if (mode === 'stats') {
        statsView.style.display = 'flex';
        listView.style.display = 'none';
        
        btnStats.style.background = 'var(--primary)';
        btnStats.style.color = 'var(--white)';
        btnStats.style.border = 'none';
        btnStats.style.boxShadow = 'var(--shadow-soft)';
        
        btnList.style.background = 'transparent';
        btnList.style.color = 'var(--primary)';
        btnList.style.border = '1px solid var(--primary)';
        btnList.style.boxShadow = 'none';
    } else {
        statsView.style.display = 'none';
        listView.style.display = 'block';
        
        btnList.style.background = 'var(--primary)';
        btnList.style.color = 'var(--white)';
        btnList.style.border = 'none';
        btnList.style.boxShadow = 'var(--shadow-soft)';
        
        btnStats.style.background = 'transparent';
        btnStats.style.color = 'var(--primary)';
        btnStats.style.border = '1px solid var(--primary)';
        btnStats.style.boxShadow = 'none';
    }
}

// --- Chart.js Data and Configuration ---
const chartDataObj = <?php echo json_encode($chartData); ?>;

let weekOffset = 0;
let happyChartInst, healthChartInst;

// Group data by date string (YYYY-MM-DD)
const dateDataMap = {};
chartDataObj.ratings.forEach(r => {
    dateDataMap[r.log_date_str] = {
        health: parseFloat(r.avg_health),
        happy: parseFloat(r.avg_happy)
    };
});

function getMonday(d) {
    d = new Date(d);
    var day = d.getDay(),
        diff = d.getDate() - day + (day == 0 ? -6: 1); // adjust when day is sunday
    return new Date(d.setDate(diff));
}

function renderCharts() {
    let refDate = new Date();
    refDate.setDate(refDate.getDate() + (weekOffset * 7));
    let monday = getMonday(refDate);

    let labels = [];
    let happyData = [];
    let healthData = [];
    const dayNames = ['Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag'];

    for(let i = 0; i < 5; i++) {
        let d = new Date(monday);
        d.setDate(monday.getDate() + i);
        
        let y = d.getFullYear();
        let m = String(d.getMonth()+1).padStart(2, '0');
        let day = String(d.getDate()).padStart(2, '0');
        let isoDate = `${y}-${m}-${day}`;

        labels.push(dayNames[i] + ' (' + day + '/' + m + ')');

        if (dateDataMap[isoDate]) {
            happyData.push(dateDataMap[isoDate].happy);
            healthData.push(dateDataMap[isoDate].health);
        } else {
            happyData.push(null);
            healthData.push(null);
        }
    }

    if (weekOffset === 0) {
        document.getElementById('weekLabel').innerText = "Denna vecka";
    } else if (weekOffset === -1) {
        document.getElementById('weekLabel').innerText = "Förra veckan";
    } else if (weekOffset > 0) {
        document.getElementById('weekLabel').innerText = "Kommande +" + weekOffset + "v";
    } else {
        document.getElementById('weekLabel').innerText = Math.abs(weekOffset) + " veckor sedan";
    }

    // Update Health Chart
    if(healthChartInst) healthChartInst.destroy();
    const ctxHealth = document.getElementById('healthChart').getContext('2d');
    healthChartInst = new Chart(ctxHealth, {
        type: 'bar',
        data: {
            labels: labels,
            datasets: [{
                label: 'Genomsnittlig Hälsa (1-5)',
                data: healthData,
                backgroundColor: 'rgba(230, 125, 79, 0.8)', 
                borderColor: 'rgba(230, 125, 79, 1)',
                borderWidth: 0,
                borderRadius: 10 
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: {
                duration: 1000,
                easing: 'easeOutQuart'
            },
            scales: { 
                y: { 
                    beginAtZero: true, 
                    max: 5,
                    grid: { display: false } 
                },
                x: {
                    grid: { display: false } 
                }
            }
        }
    });

    // Update Happy Chart
    if(happyChartInst) happyChartInst.destroy();
    const ctxHappy = document.getElementById('happyChart').getContext('2d');
    happyChartInst = new Chart(ctxHappy, {
        type: 'bar',
        data: {
            labels: labels,
            datasets: [{
                label: 'Genomsnittlig Glädje (1-3)',
                data: happyData,
                backgroundColor: 'rgba(230, 125, 79, 0.8)',
                borderColor: 'rgba(230, 125, 79, 1)',
                borderWidth: 0,
                borderRadius: 10 
            }]
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            animation: {
                duration: 1000,
                easing: 'easeOutQuart'
            },
            scales: { 
                y: { 
                    beginAtZero: true, 
                    max: 3,
                    grid: { display: false } 
                },
                x: {
                    grid: { display: false } 
                }
            }
        }
    });
}

// Ensure function is in global scope to be fired by HTML attributes
window.changeWeek = function(diff) {
    weekOffset += diff;
    renderCharts();
}
renderCharts();

// 3. Pie Chart (Top Restaurants)
const labelsPie = chartDataObj.top_restaurants.map(r => r.restaurant_name);
const dataPie = chartDataObj.top_restaurants.map(r => parseInt(r.visit_count));

const ctxPie = document.getElementById('restaurantPieChart').getContext('2d');
new Chart(ctxPie, {
    type: 'pie',
    data: {
        labels: labelsPie,
        datasets: [{
            data: dataPie,
            backgroundColor: [
                'rgba(244, 67, 54, 0.7)',
                'rgba(33, 150, 243, 0.7)',
                'rgba(255, 152, 0, 0.7)',
                'rgba(156, 39, 176, 0.7)',
                'rgba(0, 150, 136, 0.7)'
                 // Any 6th+ item caught by query limits will just not be here,
                 // unless manual grouping in PHP handles a generic "Other"
            ]
        }]
    },
    options: {
        responsive: true,
        plugins: {
            title: { display: true, text: 'Mest besökta restauranger' }
        }
    }
});
</script>

<?php require_once './includes/footer.php'?>