Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ac3977059 | ||
|
|
adc3e210b7 | ||
|
|
6255e8b900 | ||
|
|
cab661dbb4 | ||
|
|
d0ad1dc6cd | ||
|
|
a29976994c | ||
|
|
d47e350516 |
129
admin.php
Normal file
129
admin.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Check if user is logged in and is an Admin
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Handle deletion of a submission
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submission_id_to_delete'])) {
|
||||||
|
$submission_id = $_POST['submission_id_to_delete'];
|
||||||
|
|
||||||
|
// First, delete the associated answers
|
||||||
|
$delete_answers_stmt = db()->prepare("DELETE FROM survey_answers WHERE submission_id = ?");
|
||||||
|
$delete_answers_stmt->execute([$submission_id]);
|
||||||
|
|
||||||
|
// Then, delete the submission itself
|
||||||
|
$delete_submission_stmt = db()->prepare("DELETE FROM feedback_submissions WHERE id = ?");
|
||||||
|
$delete_submission_stmt->execute([$submission_id]);
|
||||||
|
|
||||||
|
// Redirect to the same page to see the changes
|
||||||
|
header('Location: admin.php?page=' . (isset($_GET['page']) ? $_GET['page'] : 1));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$records_per_page = 10;
|
||||||
|
$offset = ($page - 1) * $records_per_page;
|
||||||
|
|
||||||
|
// Get total number of submissions
|
||||||
|
$total_stmt = db()->query("SELECT COUNT(*) FROM feedback_submissions");
|
||||||
|
$total_records = $total_stmt->fetchColumn();
|
||||||
|
$total_pages = ceil($total_records / $records_per_page);
|
||||||
|
|
||||||
|
// Fetch submissions for the current page
|
||||||
|
$submissions_stmt = db()->prepare("SELECT s.id, s.name, s.email, s.created_at, sv.title as survey_title, sv.id as survey_id FROM feedback_submissions s JOIN surveys sv ON s.survey_id = sv.id ORDER BY s.created_at DESC LIMIT :limit OFFSET :offset");
|
||||||
|
$submissions_stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
|
||||||
|
$submissions_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$submissions_stmt->execute();
|
||||||
|
$submissions = $submissions_stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$pageTitle = "Admin - Feedback Submissions";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h1 class="h3">Feedback Submissions</h1>
|
||||||
|
<div>
|
||||||
|
<a href="dashboard.php" class="btn btn-info">Dashboard</a>
|
||||||
|
<a href="surveys.php" class="btn btn-success">Manage Surveys</a>
|
||||||
|
<a href="export.php" class="btn btn-primary">Export to CSV</a>
|
||||||
|
<a href="logout.php" class="btn btn-secondary">Logout</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Survey</th>
|
||||||
|
<th>Submitter</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Submitted At</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($submissions)):
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">No feedback submissions yet.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else:
|
||||||
|
?>
|
||||||
|
<?php foreach ($submissions as $submission):
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($submission['survey_title']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($submission['name']) ?></td>
|
||||||
|
<td><?= htmlspecialchars($submission['email']) ?></td>
|
||||||
|
<td><?= $submission['created_at'] ?></td>
|
||||||
|
<td>
|
||||||
|
<a href="view_submission.php?id=<?= $submission['id'] ?>" class="btn btn-sm btn-info">View</a>
|
||||||
|
<form method="POST" action="admin.php" style="display: inline-block;" onsubmit="return confirm('Are you sure you want to delete this submission?');">
|
||||||
|
<input type="hidden" name="submission_id_to_delete" value="<?= $submission['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach;
|
||||||
|
?>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Links -->
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<?php if ($page > 1): ?>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=<?= $page - 1 ?>">Previous</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||||
|
<li class="page-item <?= ($i == $page) ? 'active' : '' ?>"><a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a></li>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<?php if ($page < $total_pages): ?>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=<?= $page + 1 ?>">Next</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
88
api.php
Normal file
88
api.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'submissions_per_survey':
|
||||||
|
$stmt = db()->query("SELECT s.title, COUNT(fs.id) as submission_count FROM surveys s LEFT JOIN feedback_submissions fs ON s.id = fs.survey_id GROUP BY s.id");
|
||||||
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$labels = array_column($results, 'title');
|
||||||
|
$values = array_column($results, 'submission_count');
|
||||||
|
echo json_encode(['labels' => $labels, 'values' => $values]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'survey_question_analytics':
|
||||||
|
$surveys_stmt = db()->query("SELECT id, title FROM surveys");
|
||||||
|
$surveys = $surveys_stmt->fetchAll(PDO::FETCH_KEY_PAIR);
|
||||||
|
|
||||||
|
$questions_stmt = db()->query("SELECT survey_id, id, question_text, question_type FROM survey_questions WHERE question_type = 'rating' OR question_type = 'multiple-choice'");
|
||||||
|
$questions = $questions_stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$answers_stmt = db()->query("SELECT q.id as question_id, a.answer_text FROM survey_answers a JOIN survey_questions q ON a.question_id = q.id WHERE q.question_type = 'rating' OR q.question_type = 'multiple-choice'");
|
||||||
|
$answers_by_question = $answers_stmt->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$response = [];
|
||||||
|
foreach ($surveys as $survey_id => $survey_title) {
|
||||||
|
$survey_data = [
|
||||||
|
'id' => $survey_id,
|
||||||
|
'title' => $survey_title,
|
||||||
|
'questions' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($questions[$survey_id])) {
|
||||||
|
foreach ($questions[$survey_id] as $question) {
|
||||||
|
$question_id = $question['id'];
|
||||||
|
$question_data = [
|
||||||
|
'id' => $question_id,
|
||||||
|
'question_text' => $question['question_text'],
|
||||||
|
'type' => $question['question_type'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$answers = $answers_by_question[$question_id] ?? [];
|
||||||
|
|
||||||
|
if ($question['question_type'] == 'multiple-choice') {
|
||||||
|
$answer_counts = [];
|
||||||
|
foreach ($answers as $answer_row) {
|
||||||
|
$options = preg_split('/,\s*/', $answer_row);
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$trimmed_option = trim($option);
|
||||||
|
if (!empty($trimmed_option)) {
|
||||||
|
if (!isset($answer_counts[$trimmed_option])) {
|
||||||
|
$answer_counts[$trimmed_option] = 0;
|
||||||
|
}
|
||||||
|
$answer_counts[$trimmed_option]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$question_data['answers'] = [
|
||||||
|
'labels' => array_keys($answer_counts),
|
||||||
|
'values' => array_values($answer_counts)
|
||||||
|
];
|
||||||
|
} else { // rating
|
||||||
|
$answer_counts = array_count_values($answers);
|
||||||
|
$question_data['answers'] = [
|
||||||
|
'labels' => array_keys($answer_counts),
|
||||||
|
'values' => array_values($answer_counts)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$survey_data['questions'][] = $question_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$response[] = $survey_data;
|
||||||
|
}
|
||||||
|
echo json_encode($response);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
echo json_encode(['error' => 'Invalid action']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
78
assets/css/custom.css
Normal file
78
assets/css/custom.css
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
:root {
|
||||||
|
--primary-color: #556EE6;
|
||||||
|
--secondary-color: #F8B425;
|
||||||
|
--background-color: #F5F7FA;
|
||||||
|
--text-color: #333;
|
||||||
|
--light-gray: #E9ECEF;
|
||||||
|
--dark-gray: #7A7A7A;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #4055B2;
|
||||||
|
border-color: #4055B2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #E0A01E;
|
||||||
|
border-color: #E0A01E;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #4055B2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 1px solid var(--light-gray);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border: 1px solid var(--light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-text {
|
||||||
|
margin-left: 10px;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
92
assets/js/dashboard.js
Normal file
92
assets/js/dashboard.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Chart for submissions per survey
|
||||||
|
fetch('api.php?action=submissions_per_survey')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const ctx = document.getElementById('submissions-chart').getContext('2d');
|
||||||
|
new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: data.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: '# of Submissions',
|
||||||
|
data: data.values,
|
||||||
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||||||
|
borderColor: 'rgba(75, 192, 192, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Charts for each survey's questions
|
||||||
|
fetch('api.php?action=survey_question_analytics')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(surveys => {
|
||||||
|
const container = document.getElementById('survey-charts-container');
|
||||||
|
surveys.forEach(survey => {
|
||||||
|
const surveyRow = document.createElement('div');
|
||||||
|
surveyRow.className = 'row';
|
||||||
|
|
||||||
|
const surveyTitle = document.createElement('h2');
|
||||||
|
surveyTitle.innerText = `Survey: ${survey.title}`;
|
||||||
|
container.appendChild(surveyTitle);
|
||||||
|
container.appendChild(surveyRow);
|
||||||
|
|
||||||
|
survey.questions.forEach(question => {
|
||||||
|
if (question.type === 'rating' || question.type === 'multiple-choice') {
|
||||||
|
const col = document.createElement('div');
|
||||||
|
col.className = 'col-md-6';
|
||||||
|
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'card mb-4';
|
||||||
|
|
||||||
|
const cardHeader = document.createElement('div');
|
||||||
|
cardHeader.className = 'card-header';
|
||||||
|
cardHeader.innerHTML = `<h3>${question.question_text}</h3>`;
|
||||||
|
|
||||||
|
const cardBody = document.createElement('div');
|
||||||
|
cardBody.className = 'card-body';
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
cardBody.appendChild(canvas);
|
||||||
|
card.appendChild(cardHeader);
|
||||||
|
card.appendChild(cardBody);
|
||||||
|
col.appendChild(card);
|
||||||
|
surveyRow.appendChild(col);
|
||||||
|
|
||||||
|
new Chart(canvas.getContext('2d'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: question.answers.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Count',
|
||||||
|
data: question.answers.values,
|
||||||
|
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||||
|
borderColor: 'rgba(54, 162, 235, 1)',
|
||||||
|
borderWidth: 1
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
ticks: {
|
||||||
|
stepSize: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
115
assets/js/main.js
Normal file
115
assets/js/main.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const surveyForm = document.getElementById('survey-form');
|
||||||
|
if (!surveyForm) return;
|
||||||
|
|
||||||
|
const steps = Array.from(surveyForm.querySelectorAll('.step'));
|
||||||
|
const nextBtn = document.getElementById('nextBtn');
|
||||||
|
const prevBtn = document.getElementById('prevBtn');
|
||||||
|
const submitBtn = document.getElementById('submitBtn');
|
||||||
|
const progressBar = surveyForm.querySelector('.progress-bar');
|
||||||
|
let currentStep = 0;
|
||||||
|
|
||||||
|
function showStep(stepIndex) {
|
||||||
|
steps.forEach((step, index) => {
|
||||||
|
step.classList.toggle('active', index === stepIndex);
|
||||||
|
});
|
||||||
|
updateProgressBar();
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateProgressBar() {
|
||||||
|
const progress = ((currentStep + 1) / steps.length) * 100;
|
||||||
|
progressBar.style.width = progress + '%';
|
||||||
|
progressBar.setAttribute('aria-valuenow', progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtons() {
|
||||||
|
prevBtn.style.display = currentStep === 0 ? 'none' : 'inline-block';
|
||||||
|
nextBtn.style.display = currentStep === steps.length - 1 ? 'none' : 'inline-block';
|
||||||
|
submitBtn.style.display = currentStep === steps.length - 1 ? 'inline-block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateStep(stepIndex) {
|
||||||
|
const currentStepElement = steps[stepIndex];
|
||||||
|
const inputs = Array.from(currentStepElement.querySelectorAll('input, textarea'));
|
||||||
|
let isValid = true;
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
if (input.hasAttribute('required')) {
|
||||||
|
if (input.type === 'radio' || input.type === 'checkbox') {
|
||||||
|
const name = input.name;
|
||||||
|
if (!surveyForm.querySelector(`input[name="${name}"]:checked`)) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
} else if (!input.value.trim()) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextBtn.addEventListener('click', () => {
|
||||||
|
if (!validateStep(currentStep)) {
|
||||||
|
alert('Please answer the question before proceeding.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentStep < steps.length - 1) {
|
||||||
|
currentStep++;
|
||||||
|
showStep(currentStep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
prevBtn.addEventListener('click', () => {
|
||||||
|
if (currentStep > 0) {
|
||||||
|
currentStep--;
|
||||||
|
showStep(currentStep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
showStep(currentStep);
|
||||||
|
|
||||||
|
const successMessage = document.getElementById('success-message');
|
||||||
|
const formContainer = document.querySelector('.form-container');
|
||||||
|
|
||||||
|
surveyForm.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const emailInput = document.getElementById('email');
|
||||||
|
if (emailInput) {
|
||||||
|
formData.append('email', emailInput.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('submit_feedback.php', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
if (formContainer) {
|
||||||
|
formContainer.classList.add('hidden');
|
||||||
|
}
|
||||||
|
if (successMessage) {
|
||||||
|
successMessage.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert('An error occurred: ' + (data.error || 'Unknown error'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('A server error occurred. Please try again later.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function validateEmail(email) {
|
||||||
|
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
return re.test(String(email).toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
BIN
assets/pasted-20251007-164125-74b145ee.png
Normal file
BIN
assets/pasted-20251007-164125-74b145ee.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 458 KiB |
BIN
assets/pasted-20251007-214249-09f8d1f8.png
Normal file
BIN
assets/pasted-20251007-214249-09f8d1f8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
assets/pasted-20251007-215849-00ad5b16.png
Normal file
BIN
assets/pasted-20251007-215849-00ad5b16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/pasted-20251007-221904-8ecf5868.png
Normal file
BIN
assets/pasted-20251007-221904-8ecf5868.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
BIN
assets/pasted-20251008-162409-6b449f77.png
Normal file
BIN
assets/pasted-20251008-162409-6b449f77.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
55
dashboard.php
Normal file
55
dashboard.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Analytics Dashboard";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<h1 class="h3">Analytics Dashboard</h1>
|
||||||
|
<a href="admin.php" class="btn btn-secondary">Back to Submissions</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>Submissions per Survey</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<canvas id="submissions-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2 class="h4 mb-4">Survey Question Analytics</h2>
|
||||||
|
<div id="survey-charts-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="assets/js/dashboard.js?v=<?php echo time(); ?>"></script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
@ -8,10 +8,50 @@ define('DB_PASS', 'e45f2778-db1f-450c-99c6-29efb4601472');
|
|||||||
function db() {
|
function db() {
|
||||||
static $pdo;
|
static $pdo;
|
||||||
if (!$pdo) {
|
if (!$pdo) {
|
||||||
|
try {
|
||||||
|
// Connect to MySQL server without specifying a database
|
||||||
|
$pdo = new PDO('mysql:host='.DB_HOST.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create the database if it doesn't exist
|
||||||
|
$pdo->exec('CREATE DATABASE IF NOT EXISTS `'.DB_NAME.'`');
|
||||||
|
|
||||||
|
// Re-connect to the specific database
|
||||||
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// If database creation fails, it's a critical error.
|
||||||
|
error_log('Database connection or creation failed: ' . $e->getMessage());
|
||||||
|
// Exit or handle the error gracefully. For a web request, you might show a generic error page.
|
||||||
|
http_response_code(500);
|
||||||
|
echo "Database connection failed. Please check the server logs.";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $pdo;
|
return $pdo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function run_migrations() {
|
||||||
|
$pdo = db();
|
||||||
|
$migrations_dir = __DIR__ . '/migrations';
|
||||||
|
if (!is_dir($migrations_dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$files = glob($migrations_dir . '/*.sql');
|
||||||
|
foreach ($files as $file) {
|
||||||
|
$sql = file_get_contents($file);
|
||||||
|
try {
|
||||||
|
$pdo->exec($sql);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
// Log error or handle it as needed
|
||||||
|
error_log("Migration failed for file: " . basename($file) . " with error: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run_migrations();
|
||||||
7
db/migrations/001_create_feedback_submissions_table.sql
Normal file
7
db/migrations/001_create_feedback_submissions_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS feedback_submissions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
6
db/migrations/002_create_surveys_table.sql
Normal file
6
db/migrations/002_create_surveys_table.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS surveys (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
8
db/migrations/003_create_survey_questions_table.sql
Normal file
8
db/migrations/003_create_survey_questions_table.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS survey_questions (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
survey_id INT NOT NULL,
|
||||||
|
question_text TEXT NOT NULL,
|
||||||
|
question_type VARCHAR(50) NOT NULL, -- e.g., 'text', 'multiple-choice', 'rating'
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (survey_id) REFERENCES surveys(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE `feedback_submissions` ADD COLUMN `survey_id` INT NULL AFTER `id`;
|
||||||
9
db/migrations/005_create_survey_answers_table.sql
Normal file
9
db/migrations/005_create_survey_answers_table.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS survey_answers (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
submission_id INT NOT NULL,
|
||||||
|
question_id INT NOT NULL,
|
||||||
|
answer_text TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (submission_id) REFERENCES feedback_submissions(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (question_id) REFERENCES survey_questions(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
7
db/migrations/006_create_users_table.sql
Normal file
7
db/migrations/006_create_users_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
username VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
password VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255) NOT NULL UNIQUE,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
4
db/migrations/007_create_roles_table.sql
Normal file
4
db/migrations/007_create_roles_table.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS roles (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
role_name VARCHAR(50) NOT NULL UNIQUE
|
||||||
|
);
|
||||||
7
db/migrations/008_create_user_roles_table.sql
Normal file
7
db/migrations/008_create_user_roles_table.sql
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS user_roles (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
role_id INT NOT NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
1
db/migrations/009_insert_default_roles.sql
Normal file
1
db/migrations/009_insert_default_roles.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
INSERT IGNORE INTO `roles` (`role_name`) VALUES ('Admin'), ('Respondent');
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE feedback_submissions MODIFY COLUMN message TEXT NULL;
|
||||||
1
db/migrations/011_add_options_to_survey_questions.sql
Normal file
1
db/migrations/011_add_options_to_survey_questions.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE survey_questions ADD COLUMN options TEXT NULL AFTER question_type;
|
||||||
220
debug_log.txt
Executable file
220
debug_log.txt
Executable file
@ -0,0 +1,220 @@
|
|||||||
|
--- NEW REQUEST ---
|
||||||
|
Surveys:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[1] => Your True Flatlogic Feedback
|
||||||
|
)
|
||||||
|
|
||||||
|
Questions:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[1] => Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[survey_id] => 1
|
||||||
|
[question_text] => What stopped you from upgrading after creating your app?
|
||||||
|
[question_type] => multiple-choice
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
[2] => Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[survey_id] => 1
|
||||||
|
[question_text] => What would help you continue?
|
||||||
|
[question_type] => multiple-choice
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Answers by Question:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[1] => Array
|
||||||
|
(
|
||||||
|
[0] => The generated app didn’t look good enough, Bugs or errors on the platform, It used too many credits / hosting felt expensive
|
||||||
|
)
|
||||||
|
|
||||||
|
[2] => Array
|
||||||
|
(
|
||||||
|
[0] => A short call with an engineer, Clearer tutorials/documentation, Access to a specific template/feature
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Processing Survey ID: 1
|
||||||
|
Processing Question ID:
|
||||||
|
Answers for Question :
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
Processed Question Data:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[id] =>
|
||||||
|
[question_text] => What stopped you from upgrading after creating your app?
|
||||||
|
[type] => multiple-choice
|
||||||
|
[answers] => Array
|
||||||
|
(
|
||||||
|
[labels] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
[values] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Final Response:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[id] => 1
|
||||||
|
[title] => Your True Flatlogic Feedback
|
||||||
|
[questions] => Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[id] =>
|
||||||
|
[question_text] => What stopped you from upgrading after creating your app?
|
||||||
|
[type] => multiple-choice
|
||||||
|
[answers] => Array
|
||||||
|
(
|
||||||
|
[labels] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
[values] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
--- NEW REQUEST ---
|
||||||
|
Surveys:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[1] => Your True Flatlogic Feedback
|
||||||
|
)
|
||||||
|
|
||||||
|
Questions:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[1] => Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[survey_id] => 1
|
||||||
|
[question_text] => What stopped you from upgrading after creating your app?
|
||||||
|
[question_type] => multiple-choice
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
[2] => Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[survey_id] => 1
|
||||||
|
[question_text] => What would help you continue?
|
||||||
|
[question_type] => multiple-choice
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Answers by Question:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[1] => Array
|
||||||
|
(
|
||||||
|
[0] => The generated app didn’t look good enough, Bugs or errors on the platform, It used too many credits / hosting felt expensive
|
||||||
|
)
|
||||||
|
|
||||||
|
[2] => Array
|
||||||
|
(
|
||||||
|
[0] => A short call with an engineer, Clearer tutorials/documentation, Access to a specific template/feature
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Processing Survey ID: 1
|
||||||
|
Processing Question ID:
|
||||||
|
Answers for Question :
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
Processed Question Data:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[id] =>
|
||||||
|
[question_text] => What stopped you from upgrading after creating your app?
|
||||||
|
[type] => multiple-choice
|
||||||
|
[answers] => Array
|
||||||
|
(
|
||||||
|
[labels] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
[values] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Final Response:
|
||||||
|
Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[id] => 1
|
||||||
|
[title] => Your True Flatlogic Feedback
|
||||||
|
[questions] => Array
|
||||||
|
(
|
||||||
|
[0] => Array
|
||||||
|
(
|
||||||
|
[id] =>
|
||||||
|
[question_text] => What stopped you from upgrading after creating your app?
|
||||||
|
[type] => multiple-choice
|
||||||
|
[answers] => Array
|
||||||
|
(
|
||||||
|
[labels] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
[values] => Array
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
75
edit_survey.php
Normal file
75
edit_survey.php
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Survey ID
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$survey_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch survey details
|
||||||
|
$survey_stmt = db()->prepare("SELECT * FROM surveys WHERE id = ?");
|
||||||
|
$survey_stmt->execute([$survey_id]);
|
||||||
|
$survey = $survey_stmt->fetch();
|
||||||
|
if (!$survey) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission for updating survey
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['title'])) {
|
||||||
|
$title = trim($_POST['title']);
|
||||||
|
$description = trim($_POST['description']);
|
||||||
|
|
||||||
|
if (!empty($title)) {
|
||||||
|
$stmt = db()->prepare("UPDATE surveys SET title = ?, description = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$title, $description, $survey_id]);
|
||||||
|
header("Location: surveys.php"); // Redirect to the survey list
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Edit Survey";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1>Edit Survey</h1>
|
||||||
|
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Survey Form -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">Edit Survey</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="title" class="form-label">Survey Title</label>
|
||||||
|
<input type="text" id="title" name="title" class="form-control" value="<?= htmlspecialchars($survey['title']) ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description" class="form-label">Description (Optional)</label>
|
||||||
|
<textarea id="description" name="description" rows="3" class="form-control"><?= htmlspecialchars($survey['description']) ?></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
66
export.php
Normal file
66
export.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all submissions with their answers
|
||||||
|
$stmt = db()->query("SELECT
|
||||||
|
s.id as submission_id,
|
||||||
|
s.name as submitter_name,
|
||||||
|
s.email as submitter_email,
|
||||||
|
s.created_at as submission_date,
|
||||||
|
sv.title as survey_title,
|
||||||
|
q.question_text,
|
||||||
|
a.answer_text
|
||||||
|
FROM feedback_submissions s
|
||||||
|
JOIN surveys sv ON s.survey_id = sv.id
|
||||||
|
LEFT JOIN survey_answers a ON s.id = a.submission_id
|
||||||
|
LEFT JOIN survey_questions q ON a.question_id = q.id
|
||||||
|
ORDER BY s.created_at DESC, q.created_at ASC");
|
||||||
|
|
||||||
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Set headers for CSV download
|
||||||
|
header('Content-Type: text/csv; charset=utf-8');
|
||||||
|
header('Content-Disposition: attachment; filename=feedback_submissions_' . date('Y-m-d') . '.csv');
|
||||||
|
|
||||||
|
$output = fopen('php://output', 'w');
|
||||||
|
|
||||||
|
// Get all unique questions in order
|
||||||
|
$questions_stmt = db()->query("SELECT DISTINCT question_text FROM survey_questions ORDER BY created_at ASC");
|
||||||
|
$question_headers = $questions_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Output the column headings
|
||||||
|
$headers = array_merge(['Submission ID', 'Submitter', 'Email', 'Date', 'Survey'], $question_headers);
|
||||||
|
fputcsv($output, $headers);
|
||||||
|
|
||||||
|
// Process the results
|
||||||
|
$submissions = [];
|
||||||
|
foreach ($results as $row) {
|
||||||
|
$submissions[$row['submission_id']]['details'] = [
|
||||||
|
'submission_id' => $row['submission_id'],
|
||||||
|
'submitter_name' => $row['submitter_name'],
|
||||||
|
'submitter_email' => $row['submitter_email'],
|
||||||
|
'submission_date' => $row['submission_date'],
|
||||||
|
'survey_title' => $row['survey_title']
|
||||||
|
];
|
||||||
|
$submissions[$row['submission_id']]['answers'][$row['question_text']] = $row['answer_text'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write rows to CSV
|
||||||
|
foreach ($submissions as $submission_id => $data) {
|
||||||
|
$row = $data['details'];
|
||||||
|
foreach ($question_headers as $question) {
|
||||||
|
$row[] = $data['answers'][$question] ?? '';
|
||||||
|
}
|
||||||
|
fputcsv($output, $row);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($output);
|
||||||
|
exit;
|
||||||
|
?>
|
||||||
226
index.php
226
index.php
@ -1,150 +1,86 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
require_once __DIR__ . '/db/config.php';
|
||||||
@ini_set('display_errors', '1');
|
|
||||||
@error_reporting(E_ALL);
|
|
||||||
@date_default_timezone_set('UTC');
|
|
||||||
|
|
||||||
$phpVersion = PHP_VERSION;
|
// Pagination
|
||||||
$now = date('Y-m-d H:i:s');
|
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$records_per_page = 10;
|
||||||
|
$offset = ($page - 1) * $records_per_page;
|
||||||
|
|
||||||
|
// Get total number of surveys
|
||||||
|
$total_stmt = db()->query("SELECT COUNT(*) FROM surveys");
|
||||||
|
$total_records = $total_stmt->fetchColumn();
|
||||||
|
$total_pages = ceil($total_records / $records_per_page);
|
||||||
|
|
||||||
|
// Fetch surveys for the current page
|
||||||
|
$surveys_stmt = db()->prepare("SELECT * FROM surveys ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
|
||||||
|
$surveys_stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
|
||||||
|
$surveys_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$surveys_stmt->execute();
|
||||||
|
$surveys = $surveys_stmt->fetchAll();
|
||||||
|
|
||||||
|
$pageTitle = "Available Surveys";
|
||||||
|
$description = "Choose a survey to provide your feedback.";
|
||||||
|
|
||||||
|
require_once 'templates/header.php';
|
||||||
?>
|
?>
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
<main>
|
||||||
<head>
|
<section class="hero">
|
||||||
<meta charset="utf-8" />
|
<div class="container">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<h1>Available Surveys</h1>
|
||||||
<title>New Style</title>
|
<p>Please choose one of the surveys below to provide your feedback.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<?php if (empty($surveys)):
|
||||||
|
?>
|
||||||
|
<div class="col">
|
||||||
|
<p>No surveys available at the moment.</p>
|
||||||
|
</div>
|
||||||
|
<?php else:
|
||||||
|
?>
|
||||||
|
<?php foreach ($surveys as $survey):
|
||||||
|
?>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<h5 class="card-title"><?= htmlspecialchars($survey['title']) ?></h5>
|
||||||
|
<p class="card-text flex-grow-1"><?= htmlspecialchars($survey['description']) ?></p>
|
||||||
|
<a href="survey.php?id=<?= $survey['id'] ?>" class="btn btn-primary mt-auto">Take Survey</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<small class="text-muted">Created: <?= date('M j, Y', strtotime($survey['created_at'])) ?></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach;
|
||||||
|
?>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Links -->
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center mt-4">
|
||||||
|
<?php if ($page > 1): ?>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=<?= $page - 1 ?>">Previous</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||||
|
<li class="page-item <?= ($i == $page) ? 'active' : '' ?>"><a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a></li>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<?php if ($page < $total_pages): ?>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=<?= $page + 1 ?>">Next</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Read project preview data from environment
|
require_once 'templates/footer.php';
|
||||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
|
||||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
|
||||||
?>
|
?>
|
||||||
<?php if ($projectDescription): ?>
|
|
||||||
<!-- Meta description -->
|
|
||||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
|
||||||
<!-- Open Graph meta tags -->
|
|
||||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<!-- Twitter meta tags -->
|
|
||||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($projectImageUrl): ?>
|
|
||||||
<!-- Open Graph image -->
|
|
||||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<!-- Twitter image -->
|
|
||||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
|
||||||
<?php endif; ?>
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-color-start: #6a11cb;
|
|
||||||
--bg-color-end: #2575fc;
|
|
||||||
--text-color: #ffffff;
|
|
||||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
|
||||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
|
||||||
color: var(--text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
body::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
|
||||||
animation: bg-pan 20s linear infinite;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
@keyframes bg-pan {
|
|
||||||
0% { background-position: 0% 0%; }
|
|
||||||
100% { background-position: 100% 100%; }
|
|
||||||
}
|
|
||||||
main {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
background: var(--card-bg-color);
|
|
||||||
border: 1px solid var(--card-border-color);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 2rem;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.loader {
|
|
||||||
margin: 1.25rem auto 1.25rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
||||||
border-top-color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
.hint {
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
letter-spacing: -1px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
background: rgba(0,0,0,0.2);
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 1rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<div class="card">
|
|
||||||
<h1>Analyzing your requirements and generating your website…</h1>
|
|
||||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
|
||||||
<span class="sr-only">Loading…</span>
|
|
||||||
</div>
|
|
||||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
|
||||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
|
||||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
93
login.php
Normal file
93
login.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// If already logged in, redirect to the appropriate page
|
||||||
|
if (isset($_SESSION['user_id'])) {
|
||||||
|
if (in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: admin.php');
|
||||||
|
} else {
|
||||||
|
header('Location: index.php');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageTitle = "Login";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = trim($_POST['username']);
|
||||||
|
$password = $_POST['password'];
|
||||||
|
|
||||||
|
if (empty($username) || empty($password)) {
|
||||||
|
$error = 'Please fill out all fields.';
|
||||||
|
} else {
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
|
||||||
|
$stmt->execute([$username]);
|
||||||
|
$user = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($user && password_verify($password, $user['password'])) {
|
||||||
|
// Get user roles
|
||||||
|
$roles_stmt = $pdo->prepare("SELECT r.role_name FROM user_roles ur JOIN roles r ON ur.role_id = r.id WHERE ur.user_id = ?");
|
||||||
|
$roles_stmt->execute([$user['id']]);
|
||||||
|
$roles = $roles_stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
// Store user info in session
|
||||||
|
$_SESSION['user_id'] = $user['id'];
|
||||||
|
$_SESSION['username'] = $user['username'];
|
||||||
|
$_SESSION['email'] = $user['email'];
|
||||||
|
$_SESSION['user_roles'] = $roles;
|
||||||
|
|
||||||
|
// Redirect based on role
|
||||||
|
if (in_array('Admin', $roles)) {
|
||||||
|
header('Location: admin.php');
|
||||||
|
} else {
|
||||||
|
header('Location: index.php');
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
} else {
|
||||||
|
$error = 'Invalid username or password.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-center">Login</h1>
|
||||||
|
<?php if ($error):
|
||||||
|
?>
|
||||||
|
<div class="alert alert-danger"><?= $error ?></div>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" id="username" name="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input type="password" id="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">Login</button>
|
||||||
|
</form>
|
||||||
|
<p class="mt-3 text-center">Don't have an account? <a href="register.php">Register here</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
7
logout.php
Normal file
7
logout.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
session_unset();
|
||||||
|
session_destroy();
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
?>
|
||||||
144
manage_questions.php
Normal file
144
manage_questions.php
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Survey ID
|
||||||
|
if (!isset($_GET['survey_id'])) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$survey_id = $_GET['survey_id'];
|
||||||
|
|
||||||
|
// Fetch survey details
|
||||||
|
$survey_stmt = db()->prepare("SELECT * FROM surveys WHERE id = ?");
|
||||||
|
$survey_stmt->execute([$survey_id]);
|
||||||
|
$survey = $survey_stmt->fetch();
|
||||||
|
if (!$survey) {
|
||||||
|
header('Location: surveys.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission for new question
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['question_text'])) {
|
||||||
|
$question_text = trim($_POST['question_text']);
|
||||||
|
$question_type = trim($_POST['question_type']);
|
||||||
|
$options = ($question_type === 'multiple-choice') ? trim($_POST['options']) : null;
|
||||||
|
|
||||||
|
if (!empty($question_text) && !empty($question_type)) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO survey_questions (survey_id, question_text, question_type, options) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$survey_id, $question_text, $question_type, $options]);
|
||||||
|
header("Location: manage_questions.php?survey_id=" . $survey_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle question deletion
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_question'])) {
|
||||||
|
$question_id = $_POST['delete_question'];
|
||||||
|
$stmt = db()->prepare("DELETE FROM survey_questions WHERE id = ? AND survey_id = ?");
|
||||||
|
$stmt->execute([$question_id, $survey_id]);
|
||||||
|
header("Location: manage_questions.php?survey_id=" . $survey_id);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all questions for the survey
|
||||||
|
$questions_stmt = db()->prepare("SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY created_at ASC");
|
||||||
|
$questions_stmt->execute([$survey_id]);
|
||||||
|
$questions = $questions_stmt->fetchAll();
|
||||||
|
|
||||||
|
$pageTitle = "Manage Questions for " . htmlspecialchars($survey['title']);
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1>Manage Questions for "<?= htmlspecialchars($survey['title']) ?>"</h1>
|
||||||
|
<a href="surveys.php" class="btn btn-secondary">Back to Surveys</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Add New Question Form -->
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">Add New Question</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="question_text" class="form-label">Question Text</label>
|
||||||
|
<input type="text" id="question_text" name="question_text" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="question_type" class="form-label">Question Type</label>
|
||||||
|
<select id="question_type" name="question_type" class="form-control" required onchange="toggleOptions(this.value)">
|
||||||
|
<option value="text">Text (Single Line)</option>
|
||||||
|
<option value="textarea">Textarea (Multi-line)</option>
|
||||||
|
<option value="rating">Rating (1-5)</option>
|
||||||
|
<option value="multiple-choice">Multiple Choice</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" id="options-container" style="display: none;">
|
||||||
|
<label for="options" class="form-label">Options (comma-separated)</label>
|
||||||
|
<input type="text" id="options" name="options" class="form-control">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add Question</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List of Existing Questions -->
|
||||||
|
<h2>Existing Questions</h2>
|
||||||
|
<div class="row">
|
||||||
|
<?php if (empty($questions)):
|
||||||
|
?>
|
||||||
|
<div class="col">
|
||||||
|
<p>No questions added to this survey yet.</p>
|
||||||
|
</div>
|
||||||
|
<?php else:
|
||||||
|
?>
|
||||||
|
<?php foreach ($questions as $question):
|
||||||
|
?>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><?= htmlspecialchars($question['question_text']) ?></h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<strong>Type:</strong> <?= htmlspecialchars($question['question_type']) ?><br>
|
||||||
|
<?php if (!empty($question['options'])):
|
||||||
|
?>
|
||||||
|
<strong>Options:</strong> <?= htmlspecialchars($question['options']) ?>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<form method="POST" action="?survey_id=<?= $survey_id ?>" style="display: inline-block;">
|
||||||
|
<input type="hidden" name="delete_question" value="<?= $question['id'] ?>">
|
||||||
|
<button type="submit" class="btn btn-danger" onclick="return confirm('Are you sure you want to delete this question?')">Delete</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach;
|
||||||
|
?>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleOptions(type) {
|
||||||
|
document.getElementById('options-container').style.display = (type === 'multiple-choice') ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
37
privacy.php
Normal file
37
privacy.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = "Privacy Policy";
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?php echo htmlspecialchars($pageTitle); ?> - Your Real Feedback</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<div class="container">
|
||||||
|
<a href="/" class="logo">Your Real Feedback</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="container py-5">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<p>This is a placeholder for your privacy policy. You should replace this with your own terms.</p>
|
||||||
|
<p>Information we collect and why we collect it...</p>
|
||||||
|
<p>How we use that information...</p>
|
||||||
|
<p>etc...</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<p>© <?php echo date('Y'); ?> Your Real Feedback. All Rights Reserved.</p>
|
||||||
|
<p><a href="/privacy.php">Privacy Policy</a></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
105
register.php
Normal file
105
register.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
$pageTitle = "Register";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
|
||||||
|
$error = '';
|
||||||
|
$success = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$username = trim($_POST['username']);
|
||||||
|
$email = trim($_POST['email']);
|
||||||
|
$password = $_POST['password'];
|
||||||
|
$password_confirm = $_POST['password_confirm'];
|
||||||
|
|
||||||
|
if (empty($username) || empty($email) || empty($password)) {
|
||||||
|
$error = 'Please fill out all fields.';
|
||||||
|
} elseif ($password !== $password_confirm) {
|
||||||
|
$error = 'Passwords do not match.';
|
||||||
|
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$error = 'Invalid email format.';
|
||||||
|
} else {
|
||||||
|
$pdo = db();
|
||||||
|
// Check if username or email already exists
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
|
||||||
|
$stmt->execute([$username, $email]);
|
||||||
|
if ($stmt->fetch()) {
|
||||||
|
$error = 'Username or email already exists.';
|
||||||
|
} else {
|
||||||
|
// Insert new user
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$user_stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
|
||||||
|
$user_stmt->execute([$username, $email, $hashed_password]);
|
||||||
|
$user_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// If this is the first user (id = 1), make them an Admin. Otherwise, assign Respondent.
|
||||||
|
$user_count_stmt = $pdo->query("SELECT COUNT(*) FROM users");
|
||||||
|
$is_first_user = ($user_count_stmt->fetchColumn() == 1);
|
||||||
|
$default_role = $is_first_user ? 'Admin' : 'Respondent';
|
||||||
|
|
||||||
|
$role_stmt = $pdo->prepare("SELECT id FROM roles WHERE role_name = ?");
|
||||||
|
$role_stmt->execute([$default_role]);
|
||||||
|
$role_id = $role_stmt->fetchColumn();
|
||||||
|
if ($role_id) {
|
||||||
|
$user_role_stmt = $pdo->prepare("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)");
|
||||||
|
$user_role_stmt->execute([$user_id, $role_id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = 'Registration successful! You can now <a href="login.php">login</a>.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title text-center">Register</h1>
|
||||||
|
<?php if ($error):
|
||||||
|
?>
|
||||||
|
<div class="alert alert-danger"><?= $error ?></div>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
<?php if ($success):
|
||||||
|
?>
|
||||||
|
<div class="alert alert-success"><?= $success ?></div>
|
||||||
|
<?php else:
|
||||||
|
?>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username" class="form-label">Username</label>
|
||||||
|
<input type="text" id="username" name="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email" class="form-label">Email Address</label>
|
||||||
|
<input type="email" id="email" name="email" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input type="password" id="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password_confirm" class="form-label">Confirm Password</label>
|
||||||
|
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">Register</button>
|
||||||
|
</form>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
<p class="mt-3 text-center">Already have an account? <a href="login.php">Login here</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
64
submit_feedback.php
Normal file
64
submit_feedback.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
$response = ['success' => false, 'error' => 'Invalid request'];
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$name = trim($_POST['name'] ?? '');
|
||||||
|
$email = trim($_POST['email'] ?? '');
|
||||||
|
$survey_id = trim($_POST['survey_id'] ?? '');
|
||||||
|
$answers = $_POST['answers'] ?? [];
|
||||||
|
|
||||||
|
if (empty($name) || empty($survey_id) || empty($answers)) {
|
||||||
|
$response['error'] = 'Please fill out all fields.';
|
||||||
|
} else {
|
||||||
|
$pdo = db();
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
// Insert into feedback_submissions
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO feedback_submissions (name, email, survey_id) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $email, $survey_id]);
|
||||||
|
$submission_id = $pdo->lastInsertId();
|
||||||
|
|
||||||
|
// Insert into survey_answers
|
||||||
|
$answer_stmt = $pdo->prepare("INSERT INTO survey_answers (submission_id, question_id, answer_text) VALUES (?, ?, ?)");
|
||||||
|
foreach ($answers as $question_id => $answer_text) {
|
||||||
|
if (is_array($answer_text)) {
|
||||||
|
$answer_text = implode(', ', $answer_text);
|
||||||
|
}
|
||||||
|
if (!empty($answer_text)) {
|
||||||
|
$answer_stmt->execute([$submission_id, $question_id, $answer_text]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
$response['success'] = true;
|
||||||
|
unset($response['error']);
|
||||||
|
|
||||||
|
// Send email notification
|
||||||
|
require_once __DIR__ . '/mail/MailService.php';
|
||||||
|
$survey_stmt = $pdo->prepare("SELECT title FROM surveys WHERE id = ?");
|
||||||
|
$survey_stmt->execute([$survey_id]);
|
||||||
|
$survey_title = $survey_stmt->fetchColumn();
|
||||||
|
|
||||||
|
$subject = "New Submission for Survey: " . $survey_title;
|
||||||
|
$submission_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . '/view_submission.php?id=' . $submission_id;
|
||||||
|
$safe_name = htmlspecialchars($name);
|
||||||
|
$safe_email = htmlspecialchars($email);
|
||||||
|
$htmlBody = "<p>A new submission has been received for the survey: <strong>{$survey_title}</strong></p>\n <p><strong>Submitter:</strong> {$safe_name} ({$safe_email})</p>\n <p><a href=\"{$submission_url}\">Click here to view the full submission.</a></p>";
|
||||||
|
$textBody = "A new submission has been received for the survey: {$survey_title}. Submitter: {$name} ({$email}). View the submission here: {$submission_url}";
|
||||||
|
|
||||||
|
MailService::sendMail(null, $subject, $htmlBody, $textBody);
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
error_log($e->getMessage());
|
||||||
|
$response['error'] = 'Database error. Could not submit feedback.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($response);
|
||||||
|
|
||||||
136
survey.php
Normal file
136
survey.php
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once __DIR__ . '/db/config.php';
|
||||||
|
|
||||||
|
// Get survey ID from the URL
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$survey_id = $_GET['id'];
|
||||||
|
|
||||||
|
// --- Email Logic ---
|
||||||
|
$email = '';
|
||||||
|
$isEmailDisabled = false;
|
||||||
|
|
||||||
|
// 1. Check for email in URL parameter
|
||||||
|
if (isset($_GET['email']) && !empty($_GET['email'])) {
|
||||||
|
$email = $_GET['email'];
|
||||||
|
// Store it in a cookie for 1 year
|
||||||
|
setcookie('user_email', $email, time() + (86400 * 365), "/"); // 86400 = 1 day
|
||||||
|
$isEmailDisabled = true;
|
||||||
|
}
|
||||||
|
// 2. If not in URL, check for email in cookie
|
||||||
|
else if (isset($_COOKIE['user_email']) && !empty($_COOKIE['user_email'])) {
|
||||||
|
$email = $_COOKIE['user_email'];
|
||||||
|
$isEmailDisabled = true;
|
||||||
|
}
|
||||||
|
// 3. Otherwise, the field will be empty and enabled.
|
||||||
|
|
||||||
|
// Fetch survey details
|
||||||
|
$survey_stmt = db()->prepare("SELECT * FROM surveys WHERE id = ?");
|
||||||
|
$survey_stmt->execute([$survey_id]);
|
||||||
|
$survey = $survey_stmt->fetch();
|
||||||
|
|
||||||
|
// If survey doesn't exist, show an error or redirect
|
||||||
|
if (!$survey) {
|
||||||
|
$pageTitle = "Survey Not Found";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
echo "<main><section class='survey-section'><div class='container'><h2>Survey not found.</h2></div></section></main>";
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch questions for the survey
|
||||||
|
$questions_stmt = db()->prepare("SELECT * FROM survey_questions WHERE survey_id = ? ORDER BY created_at ASC");
|
||||||
|
$questions_stmt->execute([$survey_id]);
|
||||||
|
$questions = $questions_stmt->fetchAll();
|
||||||
|
|
||||||
|
$pageTitle = htmlspecialchars($survey['title']);
|
||||||
|
$description = htmlspecialchars($survey['description']);
|
||||||
|
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="hero">
|
||||||
|
<div class="container">
|
||||||
|
<h1><?= htmlspecialchars($survey['title']) ?></h1>
|
||||||
|
<p><?= htmlspecialchars($survey['description']) ?></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div id="success-message" class="hidden" style="text-align: center;">
|
||||||
|
<h2>Thank you!</h2>
|
||||||
|
<p>Your feedback has been submitted successfully. We appreciate you taking the time to help us improve.</p>
|
||||||
|
<p>We'll try our best to make Flatlogic better, and as a courtesy, we give you this promocode: <strong>ONLY_TRUE_FEEDBACK_OCT_2025</strong></p>
|
||||||
|
<p>It gives you <strong>free $10 credits</strong>. To use it, go to Billing -> Add Credits -> Select the $10 product and apply the promocode as shown below:</p>
|
||||||
|
<img src="assets/pasted-20251008-162409-6b449f77.png" alt="How to apply promocode" style="max-width: 100%; height: auto; border: 1px solid #ddd; margin-top: 15px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-container">
|
||||||
|
<form id="survey-form">
|
||||||
|
<input type="hidden" name="survey_id" value="<?= $survey_id ?>">
|
||||||
|
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="step">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name" class="form-label">Name</label>
|
||||||
|
<input type="text" id="name" name="name" class="form-control" value="<?= isset($_SESSION['username']) ? htmlspecialchars($_SESSION['username']) : '' ?>" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email" class="form-label">Email Address</label>
|
||||||
|
<input type="email" id="email" name="email" class="form-control" value="<?= htmlspecialchars($email) ?>" <?= $isEmailDisabled ? 'disabled' : '' ?> required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php foreach ($questions as $question): ?>
|
||||||
|
<div class="step">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label"><?= htmlspecialchars($question['question_text']) ?></label>
|
||||||
|
<?php if ($question['question_type'] === 'text'): ?>
|
||||||
|
<input type="text" name="answers[<?= $question['id'] ?>]" class="form-control" required>
|
||||||
|
<?php elseif ($question['question_type'] === 'textarea'): ?>
|
||||||
|
<textarea name="answers[<?= $question['id'] ?>]" rows="5" class="form-control" required></textarea>
|
||||||
|
<?php elseif ($question['question_type'] === 'rating'): ?>
|
||||||
|
<div class="rating">
|
||||||
|
<?php for ($i = 1; $i <= 5; $i++): ?>
|
||||||
|
<input type="radio" id="rating-<?= $question['id'] ?>-<?= $i ?>" name="answers[<?= $question['id'] ?>]" value="<?= $i ?>" required>
|
||||||
|
<label for="rating-<?= $question['id'] ?>-<?= $i ?>"><?= $i ?></label>
|
||||||
|
<?php endfor; ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($question['question_type'] === 'multiple-choice'): ?>
|
||||||
|
<?php $options = explode(',', $question['options']); ?>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<?php foreach ($options as $option): ?>
|
||||||
|
<?php $option = trim($option); ?>
|
||||||
|
<div class="checkbox-item">
|
||||||
|
<input type="checkbox" id="option-<?= htmlspecialchars($option) ?>-<?= $question['id'] ?>" name="answers[<?= $question['id'] ?>][]" value="<?= htmlspecialchars($option) ?>">
|
||||||
|
<label for="option-<?= htmlspecialchars($option) ?>-<?= $question['id'] ?>"><?= htmlspecialchars($option) ?></label>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button type="button" id="prevBtn" class="btn btn-secondary">Previous</button>
|
||||||
|
<button type="button" id="nextBtn" class="btn btn-primary">Next</button>
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary">Submit Feedback</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
123
surveys.php
Normal file
123
surveys.php
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission for new survey
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['title'])) {
|
||||||
|
$title = trim($_POST['title']);
|
||||||
|
$description = trim($_POST['description']);
|
||||||
|
|
||||||
|
if (!empty($title)) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO surveys (title, description) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$title, $description]);
|
||||||
|
header("Location: surveys.php"); // Redirect to avoid form resubmission
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
|
||||||
|
$records_per_page = 10;
|
||||||
|
$offset = ($page - 1) * $records_per_page;
|
||||||
|
|
||||||
|
// Get total number of surveys
|
||||||
|
$total_stmt = db()->query("SELECT COUNT(*) FROM surveys");
|
||||||
|
$total_records = $total_stmt->fetchColumn();
|
||||||
|
$total_pages = ceil($total_records / $records_per_page);
|
||||||
|
|
||||||
|
// Fetch surveys for the current page
|
||||||
|
$surveys_stmt = db()->prepare("SELECT * FROM surveys ORDER BY created_at DESC LIMIT :limit OFFSET :offset");
|
||||||
|
$surveys_stmt->bindValue(':limit', $records_per_page, PDO::PARAM_INT);
|
||||||
|
$surveys_stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$surveys_stmt->execute();
|
||||||
|
$surveys = $surveys_stmt->fetchAll();
|
||||||
|
|
||||||
|
$pageTitle = "Manage Surveys";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1>Manage Surveys</h1>
|
||||||
|
<a href="admin.php" class="btn btn-secondary">Back to Submissions</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create New Survey Form -->
|
||||||
|
<div class="card mb-5">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">Create New Survey</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="title" class="form-label">Survey Title</label>
|
||||||
|
<input type="text" id="title" name="title" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description" class="form-label">Description (Optional)</label>
|
||||||
|
<textarea id="description" name="description" rows="3" class="form-control"></textarea>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Create Survey</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- List of Existing Surveys -->
|
||||||
|
<h2>Existing Surveys</h2>
|
||||||
|
<div class="row">
|
||||||
|
<?php if (empty($surveys)):
|
||||||
|
?>
|
||||||
|
<div class="col">
|
||||||
|
<p>No surveys created yet.</p>
|
||||||
|
</div>
|
||||||
|
<?php else:
|
||||||
|
?>
|
||||||
|
<?php foreach ($surveys as $survey):
|
||||||
|
?>
|
||||||
|
<div class="col-md-6 mb-4">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title"><?= htmlspecialchars($survey['title']) ?></h5>
|
||||||
|
<p class="card-text"><?= htmlspecialchars($survey['description']) ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<a href="manage_questions.php?survey_id=<?= $survey['id'] ?>" class="btn btn-primary">Manage Questions</a>
|
||||||
|
<a href="edit_survey.php?id=<?= $survey['id'] ?>" class="btn btn-secondary">Edit</a>
|
||||||
|
<small class="text-muted float-right">Created: <?= date('M j, Y', strtotime($survey['created_at'])) ?></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach;
|
||||||
|
?>
|
||||||
|
<?php endif;
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Links -->
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center">
|
||||||
|
<?php if ($page > 1): ?>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=<?= $page - 1 ?>">Previous</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
|
||||||
|
<li class="page-item <?= ($i == $page) ? 'active' : '' ?>"><a class="page-link" href="?page=<?= $i ?>"><?= $i ?></a></li>
|
||||||
|
<?php endfor; ?>
|
||||||
|
<?php if ($page < $total_pages): ?>
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=<?= $page + 1 ?>">Next</a></li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
11
templates/footer.php
Normal file
11
templates/footer.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<footer class="footer mt-auto py-3 bg-light">
|
||||||
|
<div class="container text-center">
|
||||||
|
<span class="text-muted">© <?= date('Y') ?> Your Real Feedback by <a href="https://flatlogic.com/">Flatlogic</a> | <a href="https://flatlogic.com/privacy">Privacy Policy</a> | <a href="https://flatlogic.com/terms">Terms</a></span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
|
||||||
|
<script src="assets/js/main.js?v=<?php echo time(); ?>"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
59
templates/header.php
Normal file
59
templates/header.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
$pageTitle = isset($pageTitle) ? $pageTitle : 'SurveySphere';
|
||||||
|
$description = isset($description) ? $description : 'Create and manage surveys with ease.';
|
||||||
|
$canonical_url = "https://" . $_SERVER['HTTP_HOST'] . strtok($_SERVER["REQUEST_URI"], '?');
|
||||||
|
|
||||||
|
// Determine if the current page is the survey page
|
||||||
|
$isSurveyPage = basename($_SERVER['PHP_SELF']) == 'survey.php';
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title><?= $pageTitle ?></title>
|
||||||
|
<meta property="og:image" content="https://project-screens.s3.amazonaws.com/screenshots/34760/app-hero-20251007-160150.png" />
|
||||||
|
<meta property="twitter:image" content="https://project-screens.s3.amazonaws.com/screenshots/34760/app-hero-20251007-160150.png" />
|
||||||
|
<meta name="description" content="<?= $description ?>">
|
||||||
|
<link rel="canonical" href="<?= $canonical_url ?>" />
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?php echo time(); ?>">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<a class="navbar-brand" href="https://flatlogic.com/">
|
||||||
|
<img src="https://flatlogic.com/assets/icons/footer_logo-102b5debccc6a5a944601a0bc451c7b8da6e282147b7906a42818dda605383ff.svg" alt="Flatlogic" width="120">
|
||||||
|
</a>
|
||||||
|
<span class="navbar-text ml-2">Surveys</span>
|
||||||
|
</div>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<?php if (!$isSurveyPage): ?>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="index.php">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="surveys.php">Surveys</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (isset($_SESSION['user_id'])): ?>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="dashboard.php">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="logout.php">Logout</a>
|
||||||
|
</li>
|
||||||
|
<?php endif; ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
86
view_submission.php
Normal file
86
view_submission.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
session_start();
|
||||||
|
require_once 'db/config.php';
|
||||||
|
|
||||||
|
// Ensure admin is logged in
|
||||||
|
if (!isset($_SESSION['user_id']) || !in_array('Admin', $_SESSION['user_roles'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Submission ID
|
||||||
|
if (!isset($_GET['id'])) {
|
||||||
|
header('Location: admin.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$submission_id = $_GET['id'];
|
||||||
|
|
||||||
|
// Fetch submission details
|
||||||
|
$submission_stmt = db()->prepare("SELECT s.name, s.email, s.created_at, sv.title as survey_title FROM feedback_submissions s JOIN surveys sv ON s.survey_id = sv.id WHERE s.id = ?");
|
||||||
|
$submission_stmt->execute([$submission_id]);
|
||||||
|
$submission = $submission_stmt->fetch();
|
||||||
|
if (!$submission) {
|
||||||
|
header('Location: admin.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch answers for the submission
|
||||||
|
$answers_stmt = db()->prepare("SELECT q.question_text, a.answer_text FROM survey_answers a JOIN survey_questions q ON a.question_id = q.id WHERE a.submission_id = ? ORDER BY q.created_at ASC");
|
||||||
|
$answers_stmt->execute([$submission_id]);
|
||||||
|
$answers = $answers_stmt->fetchAll();
|
||||||
|
|
||||||
|
$pageTitle = "View Submission";
|
||||||
|
require_once 'templates/header.php';
|
||||||
|
?>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="survey-section">
|
||||||
|
<div class="container">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1>Submission Details</h1>
|
||||||
|
<a href="admin.php" class="btn btn-secondary">Back to All Submissions</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="mb-0">Submission for "<?= htmlspecialchars($submission['survey_title']) ?>"</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5>Submission Details</h5>
|
||||||
|
<p class="mb-1"><strong>Submitter:</strong> <?= htmlspecialchars($submission['name']) ?></p>
|
||||||
|
<p class="mb-1"><strong>Email:</strong> <?= htmlspecialchars($submission['email']) ?></p>
|
||||||
|
<p class="mb-0"><strong>Submitted At:</strong> <?= $submission['created_at'] ?></p>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
<h5>Answers</h5>
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 30%;">Question</th>
|
||||||
|
<th>Answer</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($answers as $answer):
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><?= htmlspecialchars($answer['question_text']) ?></td>
|
||||||
|
<td><?= nl2br(htmlspecialchars($answer['answer_text'])) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach;
|
||||||
|
?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'templates/footer.php';
|
||||||
|
?>
|
||||||
Loading…
x
Reference in New Issue
Block a user