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)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
<?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;">← 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 →</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'; ?>