adding rating system
This commit is contained in:
parent
54fe86501d
commit
5914657321
88
INSTALL.md
Normal file
88
INSTALL.md
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
# Installation Guide
|
||||||
|
|
||||||
|
Follow these steps to set up the application on your server.
|
||||||
|
|
||||||
|
## 1. Requirements
|
||||||
|
|
||||||
|
Before you begin, ensure your server meets the following requirements:
|
||||||
|
|
||||||
|
* **Operating System:** Linux, Windows, or macOS.
|
||||||
|
* **Web Server:** Apache 2.4+ (with `mod_rewrite` enabled).
|
||||||
|
* **PHP:** Version 8.0 or higher.
|
||||||
|
* **Database:** MariaDB 10.4+ or MySQL 5.7+.
|
||||||
|
* **PHP Extensions:**
|
||||||
|
* `pdo_mysql`
|
||||||
|
* `curl`
|
||||||
|
* `gd` (for image processing)
|
||||||
|
* `mbstring`
|
||||||
|
* `json`
|
||||||
|
|
||||||
|
## 2. Database Setup
|
||||||
|
|
||||||
|
1. **Create a Database:** Create a new MySQL/MariaDB database (e.g., `pos_system`).
|
||||||
|
2. **Configure Connection:** Rename `db/config.php.example` to `db/config.php` (if not already present) and update the database credentials:
|
||||||
|
```php
|
||||||
|
define('DB_HOST', 'localhost');
|
||||||
|
define('DB_NAME', 'your_database_name');
|
||||||
|
define('DB_USER', 'your_username');
|
||||||
|
define('DB_PASS', 'your_password');
|
||||||
|
```
|
||||||
|
3. **Import Schema:** Import the base schema from `db/schema.sql` into your database.
|
||||||
|
4. **Run Migrations:** Import all SQL files located in `db/migrations/` in sequential order (001, 002, etc.).
|
||||||
|
5. **Initialize Data:** Run the initialization script by visiting `http://your-domain.com/db/init.php` in your browser. This will seed categories, products, and outlets.
|
||||||
|
|
||||||
|
## 3. Creating a Super Admin
|
||||||
|
|
||||||
|
Since the initial setup does not include a default user for security reasons, you must create the first Administrator account.
|
||||||
|
|
||||||
|
### Option A: Using the Setup Script (Recommended)
|
||||||
|
Create a temporary file named `setup_admin.php` in the root directory with the following content:
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'includes/functions.php';
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$username = 'admin';
|
||||||
|
$password = password_hash('admin123', PASSWORD_DEFAULT);
|
||||||
|
$full_name = 'Super Admin';
|
||||||
|
$email = 'admin@example.com';
|
||||||
|
|
||||||
|
// Ensure the Administrator group exists
|
||||||
|
$stmt = $pdo->prepare("SELECT id FROM user_groups WHERE name = 'Administrator' LIMIT 1");
|
||||||
|
$stmt->execute();
|
||||||
|
$group_id = $stmt->fetchColumn();
|
||||||
|
|
||||||
|
if (!$group_id) {
|
||||||
|
$pdo->exec("INSERT INTO user_groups (name, permissions) VALUES ('Administrator', 'all')");
|
||||||
|
$group_id = $pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the user
|
||||||
|
try {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO users (group_id, username, password, full_name, email, is_active) VALUES (?, ?, ?, ?, ?, 1)");
|
||||||
|
$stmt->execute([$group_id, $username, $password, $full_name, $email]);
|
||||||
|
echo "Super Admin created successfully!<br>Username: admin<br>Password: admin123<br><b>Please delete this file immediately!</b>";
|
||||||
|
} catch (Exception $e) {
|
||||||
|
echo "Error: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit `http://your-domain.com/setup_admin.php` and then **delete the file**.
|
||||||
|
|
||||||
|
### Option B: Manual SQL
|
||||||
|
If you prefer SQL, run the following (replace the password hash with one generated via `password_hash()` in PHP):
|
||||||
|
```sql
|
||||||
|
INSERT INTO users (group_id, username, password, full_name, email, is_active)
|
||||||
|
VALUES (1, 'admin', 'REPLACE_WITH_HASH', 'Super Admin', 'admin@example.com', 1);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Final Steps
|
||||||
|
|
||||||
|
1. **File Permissions:** Ensure the `assets/images/` directory is writable by the web server for uploading product and ad images.
|
||||||
|
2. **Login:** Go to `http://your-domain.com/login.php` and log in with your new credentials.
|
||||||
|
3. **Company Settings:** Navigate to **Admin -> Company Settings** to configure your restaurant name, address, and currency.
|
||||||
|
|
||||||
|
---
|
||||||
|
**Security Note:** Always ensure your `db/config.php` and `.env` files are not accessible from the public web.
|
||||||
@ -457,7 +457,7 @@ function can_view($module) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$userGroupPages = ['users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php', 'attendance.php'];
|
$userGroupPages = [ 'ratings.php','users.php', 'user_edit.php', 'user_groups.php', 'user_group_edit.php', 'attendance.php'];
|
||||||
$canViewUserGroup = can_view('users') || can_view('user_groups');
|
$canViewUserGroup = can_view('users') || can_view('user_groups');
|
||||||
if ($canViewUserGroup):
|
if ($canViewUserGroup):
|
||||||
?>
|
?>
|
||||||
@ -488,6 +488,11 @@ function can_view($module) {
|
|||||||
<i class="bi bi-calendar-check me-2"></i> Attendance
|
<i class="bi bi-calendar-check me-2"></i> Attendance
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= isActive('ratings.php') ?>" href="ratings.php">
|
||||||
|
<i class="bi bi-star me-2"></i> Staff Ratings
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
3
admin/rating.php
Normal file
3
admin/rating.php
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
header('Location: ratings.php');
|
||||||
|
exit;
|
||||||
227
admin/ratings.php
Normal file
227
admin/ratings.php
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'includes/header.php';
|
||||||
|
|
||||||
|
// Check permission
|
||||||
|
if (function_exists('require_permission')) {
|
||||||
|
require_permission('users_view'); // Reuse user view permission for ratings
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo = db();
|
||||||
|
$tab = $_GET['tab'] ?? 'staff';
|
||||||
|
|
||||||
|
// Fetch Staff summary stats
|
||||||
|
$summaryStmt = $pdo->query("
|
||||||
|
SELECT u.id, u.full_name, u.username, u.profile_pic,
|
||||||
|
AVG(r.rating) as avg_rating, COUNT(r.id) as total_ratings
|
||||||
|
FROM users u
|
||||||
|
JOIN staff_ratings r ON u.id = r.user_id
|
||||||
|
GROUP BY u.id
|
||||||
|
ORDER BY avg_rating DESC
|
||||||
|
");
|
||||||
|
$summaries = $summaryStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Fetch Service summary stats
|
||||||
|
$serviceSummaryStmt = $pdo->query("
|
||||||
|
SELECT AVG(rating) as avg_rating, COUNT(id) as total_ratings FROM service_ratings
|
||||||
|
");
|
||||||
|
$serviceSummary = $serviceSummaryStmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($tab === 'service') {
|
||||||
|
$query = "SELECT * FROM service_ratings ORDER BY created_at DESC";
|
||||||
|
} else {
|
||||||
|
$query = "
|
||||||
|
SELECT r.*, u.full_name, u.username, u.profile_pic
|
||||||
|
FROM staff_ratings r
|
||||||
|
JOIN users u ON r.user_id = u.id
|
||||||
|
ORDER BY r.created_at DESC
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
$pagination = paginate_query($pdo, $query);
|
||||||
|
$ratings = $pagination['data'];
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h2 class="h3 mb-0 text-gray-800">Ratings & Feedback</h2>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<button class="btn btn-warning btn-sm rounded-pill" onclick="showRatingQR()">
|
||||||
|
<i class="bi bi-qr-code"></i> Share Rating QR
|
||||||
|
</button>
|
||||||
|
<a href="../rate.php" target="_blank" class="btn btn-outline-primary btn-sm rounded-pill">
|
||||||
|
<i class="bi bi-box-arrow-up-right"></i> Open Public Rating Page
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="nav nav-pills mb-4 bg-white p-2 rounded-4 shadow-sm d-inline-flex">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link rounded-pill <?= $tab === 'staff' ? 'active' : '' ?>" href="?tab=staff">
|
||||||
|
<i class="bi bi-people me-1"></i> Staff Performance
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link rounded-pill <?= $tab === 'service' ? 'active' : '' ?>" href="?tab=service">
|
||||||
|
<i class="bi bi-shop me-1"></i> Restaurant Service
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<?php if ($tab === 'staff'): ?>
|
||||||
|
<!-- Staff Summary Cards -->
|
||||||
|
<?php if (empty($summaries)): ?>
|
||||||
|
<div class="alert alert-info border-0 shadow-sm rounded-4 text-center py-4 mb-5">
|
||||||
|
<i class="bi bi-info-circle fs-2 mb-2"></i>
|
||||||
|
<p class="mb-0">No staff members have been rated yet.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="row g-4 mb-5">
|
||||||
|
<?php foreach ($summaries as $summary): ?>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card border-0 shadow-sm rounded-4 h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<?php if ($summary['profile_pic']): ?>
|
||||||
|
<img src="../<?= htmlspecialchars($summary['profile_pic']) ?>" alt="Staff" class="rounded-circle mb-3" style="width: 64px; height: 64px; object-fit: cover;">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="rounded-circle bg-light d-inline-flex align-items-center justify-content-center mb-3" style="width: 64px; height: 64px;">
|
||||||
|
<i class="bi bi-person fs-2 text-primary"></i>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<h5 class="card-title mb-1 small fw-bold text-truncate" title="<?= htmlspecialchars($summary['full_name'] ?: $summary['username']) ?>">
|
||||||
|
<?= htmlspecialchars($summary['full_name'] ?: $summary['username']) ?>
|
||||||
|
</h5>
|
||||||
|
<div class="text-warning mb-2">
|
||||||
|
<?php
|
||||||
|
$avg = round($summary['avg_rating']);
|
||||||
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
|
echo $i <= $avg ? '<i class="bi bi-star-fill"></i>' : '<i class="bi bi-star"></i>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<span class="text-muted ms-1">(<?= number_format($summary['avg_rating'], 1) ?>)</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-muted small mb-0"><?= $summary['total_ratings'] ?> total ratings</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<!-- Service Summary Card -->
|
||||||
|
<div class="row mb-5">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-0 shadow-sm rounded-4 bg-primary text-white">
|
||||||
|
<div class="card-body text-center py-4">
|
||||||
|
<i class="bi bi-shop fs-1 mb-3"></i>
|
||||||
|
<h4 class="fw-bold mb-1">Overall Service</h4>
|
||||||
|
<div class="fs-2 fw-bold mb-2">
|
||||||
|
<?php if ($serviceSummary['total_ratings'] > 0): ?>
|
||||||
|
<?= number_format($serviceSummary['avg_rating'], 1) ?> / 5.0
|
||||||
|
<?php else: ?>
|
||||||
|
N/A
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<div class="text-white-50">
|
||||||
|
Based on <?= $serviceSummary['total_ratings'] ?> customer reviews
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Detailed List -->
|
||||||
|
<div class="card border-0 shadow-sm rounded-4">
|
||||||
|
<div class="card-header bg-white py-3">
|
||||||
|
<h5 class="card-title mb-0"><?= $tab === 'service' ? 'Service Feedback' : 'Staff Feedback' ?></h5>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="bg-light">
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<?php if ($tab === 'staff'): ?><th>Staff Member</th><?php endif; ?>
|
||||||
|
<th>Rating</th>
|
||||||
|
<th>Comment</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if (empty($ratings)): ?>
|
||||||
|
<tr>
|
||||||
|
<td colspan="<?= $tab === 'staff' ? 4 : 3 ?>" class="text-center py-4 text-muted">No ratings found yet.</td>
|
||||||
|
</tr>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($ratings as $rating): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="small text-muted">
|
||||||
|
<?= date('M d, Y H:i', strtotime($rating['created_at'])) ?>
|
||||||
|
</td>
|
||||||
|
<?php if ($tab === 'staff'): ?>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<?php if ($rating['profile_pic']): ?>
|
||||||
|
<img src="../<?= htmlspecialchars($rating['profile_pic']) ?>" class="rounded-circle me-2" style="width: 32px; height: 32px; object-fit: cover;">
|
||||||
|
<?php endif; ?>
|
||||||
|
<span><?= htmlspecialchars($rating['full_name'] ?: $rating['username']) ?></span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<?php endif; ?>
|
||||||
|
<td>
|
||||||
|
<div class="text-warning">
|
||||||
|
<?php
|
||||||
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
|
echo $i <= $rating['rating'] ? '<i class="bi bi-star-fill"></i>' : '<i class="bi bi-star text-muted"></i>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="text-muted small"><?= htmlspecialchars($rating['comment'] ?: '-') ?></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer bg-white">
|
||||||
|
<?php render_pagination_controls($pagination); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Rating QR Modal -->
|
||||||
|
<div class="modal fade" id="ratingQRModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 shadow">
|
||||||
|
<div class="modal-header bg-warning">
|
||||||
|
<h5 class="modal-title fw-bold text-dark"><i class="bi bi-star-fill me-2"></i> Customer Rating QR</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center py-4">
|
||||||
|
<p class="text-muted mb-4">Show this QR code to the customer to rate our staff and service.</p>
|
||||||
|
<div class="mb-4">
|
||||||
|
<img id="rating-qr-img" src="" alt="Rating QR Code" class="img-fluid border p-3 bg-white shadow-sm" style="max-width: 250px;">
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-light border small text-break text-center mb-0" id="rating-url-text"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer border-top-0 justify-content-center pb-4">
|
||||||
|
<button class="btn btn-primary px-4 fw-bold" onclick="window.print()">
|
||||||
|
<i class="bi bi-printer me-2"></i> Print QR Code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const BASE_URL = '<?= get_base_url() ?>';
|
||||||
|
|
||||||
|
function showRatingQR() {
|
||||||
|
const ratingUrl = BASE_URL + 'rate.php';
|
||||||
|
document.getElementById('rating-qr-img').src = 'https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=' + encodeURIComponent(ratingUrl);
|
||||||
|
document.getElementById('rating-url-text').textContent = ratingUrl;
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('ratingQRModal'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php require_once 'includes/footer.php'; ?>
|
||||||
@ -26,6 +26,7 @@ if ($id) {
|
|||||||
'group_id' => '',
|
'group_id' => '',
|
||||||
'employee_id' => '',
|
'employee_id' => '',
|
||||||
'is_active' => 1,
|
'is_active' => 1,
|
||||||
|
'is_ratable' => 0,
|
||||||
'profile_pic' => '',
|
'profile_pic' => '',
|
||||||
'created_at' => date('Y-m-d H:i:s')
|
'created_at' => date('Y-m-d H:i:s')
|
||||||
];
|
];
|
||||||
@ -40,6 +41,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$group_id = $_POST['group_id'];
|
$group_id = $_POST['group_id'];
|
||||||
$employee_id = $_POST['employee_id'] ?? null;
|
$employee_id = $_POST['employee_id'] ?? null;
|
||||||
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
$is_active = isset($_POST['is_active']) ? 1 : 0;
|
||||||
|
$is_ratable = isset($_POST['is_ratable']) ? 1 : 0;
|
||||||
$assigned_outlets = $_POST['outlets'] ?? [];
|
$assigned_outlets = $_POST['outlets'] ?? [];
|
||||||
$password = $_POST['password'] ?? '';
|
$password = $_POST['password'] ?? '';
|
||||||
|
|
||||||
@ -60,8 +62,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
try {
|
try {
|
||||||
if ($id) {
|
if ($id) {
|
||||||
// Update
|
// Update
|
||||||
$sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, employee_id = ? WHERE id = ?";
|
$sql = "UPDATE users SET full_name = ?, username = ?, email = ?, group_id = ?, is_active = ?, is_ratable = ?, employee_id = ? WHERE id = ?";
|
||||||
$params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $id];
|
$params = [$full_name, $username, $email, $group_id, $is_active, $is_ratable, $employee_id, $id];
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
|
|
||||||
@ -73,8 +75,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
} else {
|
} else {
|
||||||
// Insert
|
// Insert
|
||||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
$sql = "INSERT INTO users (full_name, username, email, group_id, is_active, employee_id, password) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
$sql = "INSERT INTO users (full_name, username, email, group_id, is_active, is_ratable, employee_id, password) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||||
$params = [$full_name, $username, $email, $group_id, $is_active, $employee_id, $hashed_password];
|
$params = [$full_name, $username, $email, $group_id, $is_active, $is_ratable, $employee_id, $hashed_password];
|
||||||
$stmt = $pdo->prepare($sql);
|
$stmt = $pdo->prepare($sql);
|
||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
$user_id = $pdo->lastInsertId();
|
$user_id = $pdo->lastInsertId();
|
||||||
@ -237,10 +239,19 @@ include 'includes/header.php';
|
|||||||
<div class="form-text mt-1">Assign one or more outlets to this user.</div>
|
<div class="form-text mt-1">Assign one or more outlets to this user.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="row mb-4">
|
||||||
<div class="form-check form-switch">
|
<div class="col-md-6">
|
||||||
<input class="form-check-input" type="checkbox" name="is_active" id="isActiveSwitch" <?= $user['is_active'] ? 'checked' : '' ?>>
|
<div class="form-check form-switch">
|
||||||
<label class="form-check-label fw-bold text-muted small" for="isActiveSwitch">ACTIVE ACCOUNT</label>
|
<input class="form-check-input" type="checkbox" name="is_active" id="isActiveSwitch" <?= $user['is_active'] ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label fw-bold text-muted small" for="isActiveSwitch">ACTIVE ACCOUNT</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" name="is_ratable" id="isRatableSwitch" <?= $user['is_ratable'] ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label fw-bold text-muted small" for="isRatableSwitch">DISPLAY IN STAFF RATINGS</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-text small mt-1">Enable this to allow customers to rate this staff member in the public rating page.</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -74,8 +74,9 @@ include 'includes/header.php';
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="ps-4">User</th>
|
<th class="ps-4">User</th>
|
||||||
<th>Role / Group</th>
|
<th>Role / Group</th>
|
||||||
<th>Employee ID</th>
|
<th>Emp. ID</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
|
<th>Ratable</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Joined</th>
|
<th>Joined</th>
|
||||||
<th class="text-end pe-4">Actions</th>
|
<th class="text-end pe-4">Actions</th>
|
||||||
@ -104,7 +105,15 @@ include 'includes/header.php';
|
|||||||
<?= htmlspecialchars($user['group_name'] ?: 'No Group') ?>
|
<?= htmlspecialchars($user['group_name'] ?: 'No Group') ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td><?= htmlspecialchars($user['employee_id'] ?: '-') ?></td>
|
||||||
<td><?= htmlspecialchars($user['email'] ?: '-') ?></td>
|
<td><?= htmlspecialchars($user['email'] ?: '-') ?></td>
|
||||||
|
<td>
|
||||||
|
<?php if ($user['is_ratable']): ?>
|
||||||
|
<span class="badge bg-warning bg-opacity-10 text-warning border border-warning border-opacity-25 px-2 rounded-pill"><i class="bi bi-star-fill small"></i> Yes</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 px-2 rounded-pill">No</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($user['is_active']): ?>
|
<?php if ($user['is_active']): ?>
|
||||||
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 px-3 rounded-pill">Active</span>
|
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25 px-3 rounded-pill">Active</span>
|
||||||
|
|||||||
8
db/migrations/020_staff_ratings.sql
Normal file
8
db/migrations/020_staff_ratings.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS staff_ratings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||||
|
comment TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
5
db/migrations/021_add_is_ratable_to_users.sql
Normal file
5
db/migrations/021_add_is_ratable_to_users.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-- Migration: Add is_ratable to users table
|
||||||
|
-- Description: Allows choosing specific staff members to be rated by customers.
|
||||||
|
-- Date: 2026-02-23
|
||||||
|
|
||||||
|
ALTER TABLE users ADD COLUMN is_ratable TINYINT(1) DEFAULT 0;
|
||||||
6
db/migrations/022_service_ratings.sql
Normal file
6
db/migrations/022_service_ratings.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS service_ratings (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||||
|
comment TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
@ -289,3 +289,19 @@ function require_permission($permission) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base URL of the application.
|
||||||
|
*
|
||||||
|
* @return string The base URL.
|
||||||
|
*/
|
||||||
|
function get_base_url() {
|
||||||
|
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
|
||||||
|
$domainName = $_SERVER['HTTP_HOST'];
|
||||||
|
// Remove admin/ if we are in it
|
||||||
|
$script_dir = dirname($_SERVER['SCRIPT_NAME']);
|
||||||
|
$script_dir = str_replace(['/admin', '/api'], '', $script_dir);
|
||||||
|
if ($script_dir === '/') $script_dir = '';
|
||||||
|
|
||||||
|
return $protocol . $domainName . $script_dir . '/';
|
||||||
|
}
|
||||||
|
|||||||
279
index.php
279
index.php
@ -9,6 +9,7 @@ $settings = get_company_settings();
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title><?= htmlspecialchars($settings['company_name']) ?> - Welcome</title>
|
<title><?= htmlspecialchars($settings['company_name']) ?> - Welcome</title>
|
||||||
|
<meta name="description" content="Welcome to <?= htmlspecialchars($settings['company_name']) ?> POS System. Manage your business efficiently.">
|
||||||
<?php if (!empty($settings['favicon_url'])): ?>
|
<?php if (!empty($settings['favicon_url'])): ?>
|
||||||
<link rel="icon" href="<?= htmlspecialchars($settings['favicon_url']) ?>">
|
<link rel="icon" href="<?= htmlspecialchars($settings['favicon_url']) ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@ -17,128 +18,226 @@ $settings = get_company_settings();
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-gradient: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
|
||||||
|
--accent-color: #f43f5e;
|
||||||
|
--bg-soft: #f8fafc;
|
||||||
|
--card-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: var(--bg-soft);
|
||||||
|
color: #1e293b;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
/* Background Decorations */
|
||||||
|
.shape {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
filter: blur(80px);
|
||||||
|
opacity: 0.4;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.shape-1 {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
background: #6366f1;
|
||||||
|
top: -100px;
|
||||||
|
right: -100px;
|
||||||
|
}
|
||||||
|
.shape-2 {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
background: #a855f7;
|
||||||
|
bottom: -50px;
|
||||||
|
left: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-card {
|
.hero-card {
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
border-radius: 32px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow: var(--card-shadow);
|
||||||
|
padding: 3rem;
|
||||||
|
max-width: 1000px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-logo {
|
||||||
|
max-height: 100px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-title {
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.welcome-subtitle {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: #64748b;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portal-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portal-card {
|
||||||
|
background: #ffffff;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
box-shadow: 0 10px 40px rgba(0,0,0,0.08);
|
padding: 2.5rem;
|
||||||
backdrop-filter: blur(10px);
|
text-decoration: none;
|
||||||
border: 1px solid rgba(255,255,255,0.5);
|
color: inherit;
|
||||||
transition: transform 0.3s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
border: 1px solid #f1f5f9;
|
||||||
.hero-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
}
|
|
||||||
.action-btn {
|
|
||||||
height: 120px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
position: relative;
|
||||||
border-radius: 16px;
|
overflow: hidden;
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
}
|
}
|
||||||
.action-btn i {
|
|
||||||
font-size: 2.5rem;
|
.portal-card:hover {
|
||||||
margin-bottom: 10px;
|
transform: translateY(-10px);
|
||||||
|
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.15);
|
||||||
|
border-color: #e2e8f0;
|
||||||
}
|
}
|
||||||
.btn-dine-in {
|
|
||||||
background-color: #e3f2fd;
|
.portal-card i {
|
||||||
color: #0d6efd;
|
font-size: 3.5rem;
|
||||||
border-color: #bbdefb;
|
margin-bottom: 1.5rem;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
.btn-dine-in:hover {
|
|
||||||
background-color: #bbdefb;
|
.portal-card:hover i {
|
||||||
color: #0b5ed7;
|
transform: scale(1.1) rotate(5deg);
|
||||||
}
|
}
|
||||||
.btn-online {
|
|
||||||
background-color: #e8f5e9;
|
.portal-card.pos i { color: #6366f1; }
|
||||||
color: #198754;
|
.portal-card.admin i { color: #ec4899; }
|
||||||
border-color: #c8e6c9;
|
.portal-card.feedback i { color: #f59e0b; }
|
||||||
|
|
||||||
|
.portal-card h3 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.btn-online:hover {
|
|
||||||
background-color: #c8e6c9;
|
.portal-card p {
|
||||||
color: #146c43;
|
font-size: 0.9rem;
|
||||||
}
|
color: #64748b;
|
||||||
.company-logo {
|
margin-bottom: 0;
|
||||||
max-height: 80px;
|
|
||||||
width: auto;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 40px;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #6c757d;
|
color: #94a3b8;
|
||||||
font-size: 0.85rem;
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-object {
|
||||||
|
position: absolute;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: var(--primary-gradient);
|
||||||
|
border-radius: 8px;
|
||||||
|
opacity: 0.1;
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0) rotate(0); }
|
||||||
|
50% { transform: translateY(-20px) rotate(15deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.hero-card { padding: 2rem 1.5rem; }
|
||||||
|
.welcome-title { font-size: 2rem; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<div class="container">
|
<div class="shape shape-1"></div>
|
||||||
<div class="row justify-content-center">
|
<div class="shape shape-2"></div>
|
||||||
<div class="col-md-8 col-lg-6">
|
|
||||||
<div class="hero-card p-5 text-center">
|
|
||||||
|
|
||||||
<?php if (!empty($settings['logo_url'])): ?>
|
<div class="main-container">
|
||||||
<img src="<?= htmlspecialchars($settings['logo_url']) ?>" alt="Logo" class="company-logo">
|
<div class="hero-card">
|
||||||
<?php else: ?>
|
|
||||||
<div class="mb-4">
|
|
||||||
<i class="bi bi-shop fs-1 text-primary"></i>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<h1 class="fw-bold mb-2"><?= htmlspecialchars($settings['company_name']) ?></h1>
|
|
||||||
<p class="text-muted mb-5">Welcome! How would you like to order?</p>
|
|
||||||
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-6">
|
|
||||||
<a href="pos.php?order_type=dine-in" class="text-decoration-none">
|
|
||||||
<div class="action-btn btn-dine-in">
|
|
||||||
<i class="bi bi-qr-code-scan"></i>
|
|
||||||
<span>Dine In / QR</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<a href="pos.php?order_type=takeaway" class="text-decoration-none">
|
|
||||||
<div class="action-btn btn-online">
|
|
||||||
<i class="bi bi-bag-check"></i>
|
|
||||||
<span>Online Order</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5 pt-4 border-top">
|
|
||||||
<a href="admin/" class="text-muted small text-decoration-none">
|
|
||||||
<i class="bi bi-shield-lock me-1"></i> Staff Login
|
|
||||||
</a>
|
|
||||||
<a href="kitchen.php" class="text-muted small text-decoration-none ms-3">
|
|
||||||
<i class="bi bi-egg-fried me-1"></i> Kitchen Display
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<?php if (!empty($settings['logo_url'])): ?>
|
||||||
|
<img src="<?= htmlspecialchars($settings['logo_url']) ?>" alt="Logo" class="company-logo">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="mb-4">
|
||||||
|
<i class="bi bi-grid-1x2-fill fs-1 text-primary"></i>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="footer">
|
<h1 class="welcome-title"><?= htmlspecialchars($settings['company_name']) ?></h1>
|
||||||
<p>© <?= date('Y') ?> <?= htmlspecialchars($settings['company_name']) ?>. All rights reserved.</p>
|
<p class="welcome-subtitle">Your all-in-one business management solution.</p>
|
||||||
<p>Powered By Abidarcafe @2026</p>
|
|
||||||
</div>
|
<div class="portal-grid">
|
||||||
|
<!-- POS Terminal -->
|
||||||
|
<a href="pos.php" class="portal-card pos">
|
||||||
|
<div class="floating-object" style="top: 10%; right: 10%;"></div>
|
||||||
|
<i class="bi bi-pc-display-horizontal"></i>
|
||||||
|
<h3>POS Terminal</h3>
|
||||||
|
<p>Process sales, manage orders, and handle payments.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Management Console -->
|
||||||
|
<a href="admin/" class="portal-card admin">
|
||||||
|
<div class="floating-object" style="bottom: 10%; left: 10%; animation-delay: 2s; background: linear-gradient(135deg, #ec4899 0%, #f43f5e 100%);"></div>
|
||||||
|
<i class="bi bi-speedometer2"></i>
|
||||||
|
<h3>Admin Dashboard</h3>
|
||||||
|
<p>Inventory, reports, staff management, and settings.</p>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Customer Feedback -->
|
||||||
|
<a href="rate.php" class="portal-card feedback">
|
||||||
|
<div class="floating-object" style="top: 20%; left: 20%; animation-delay: 1s; background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);"></div>
|
||||||
|
<i class="bi bi-star"></i>
|
||||||
|
<h3>Rate Our Service</h3>
|
||||||
|
<p>Tell us about your experience and rate our staff.</p>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 pt-4">
|
||||||
|
<p class="text-muted small">
|
||||||
|
<i class="bi bi-info-circle me-1"></i> Authorized staff access only.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p class="mb-1">© <?= date('Y') ?> <?= htmlspecialchars($settings['company_name']) ?>. All rights reserved.</p>
|
||||||
|
<p class="mb-0">Powered By Flatlogic POS Solution</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
181
pos.php
181
pos.php
@ -110,7 +110,7 @@ if (!$loyalty_settings) {
|
|||||||
<span class="fw-bold d-none d-md-block"><?= htmlspecialchars($settings['company_name']) ?></span>
|
<span class="fw-bold d-none d-md-block"><?= htmlspecialchars($settings['company_name']) ?></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<?php if (has_permission('kitchen_view')): ?>
|
<?php if (has_permission('kitchen_view')): ?>
|
||||||
<a href="kitchen.php" class="btn btn-sm btn-outline-secondary">Kitchen View</a>
|
<a href="kitchen.php" class="btn btn-sm btn-outline-secondary">Kitchen View</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@ -125,8 +125,12 @@ if (!$loyalty_settings) {
|
|||||||
<a href="admin/orders.php" class="btn btn-sm btn-outline-secondary">Current Orders</a>
|
<a href="admin/orders.php" class="btn btn-sm btn-outline-secondary">Current Orders</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<button class="btn btn-sm btn-outline-warning" onclick="showRatingQR()">
|
||||||
|
<i class="bi bi-qr-code"></i> Rating QR
|
||||||
|
</button>
|
||||||
|
|
||||||
<div id="current-table-display" class="badge bg-light text-dark border px-3 py-2" style="display: none; font-size: 0.9rem;">
|
<div id="current-table-display" class="badge bg-light text-dark border px-3 py-2" style="display: none; font-size: 0.9rem;">
|
||||||
Table <?= htmlspecialchars($table_id) ?>
|
Table <?= htmlspecialchars((string)$table_id) ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
@ -248,107 +252,65 @@ if (!$loyalty_settings) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right: Cart & Order Info -->
|
<!-- Right Sidebar: Cart -->
|
||||||
<div class="col-md-3 col-12 cart-sidebar">
|
<div class="col-md-3 d-none d-md-flex cart-sidebar p-0">
|
||||||
|
<div class="p-3 border-bottom d-flex justify-content-between align-items-center">
|
||||||
|
<h6 class="fw-bold mb-0">Current Order</h6>
|
||||||
|
<span class="badge bg-primary rounded-pill" id="cart-count">0 items</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Top Section: Customer & Type -->
|
<!-- Customer Selection -->
|
||||||
<div class="p-3 border-bottom bg-white">
|
<div class="p-2 border-bottom bg-light">
|
||||||
<!-- Order Type -->
|
<div class="input-group input-group-sm mb-1 position-relative">
|
||||||
<div class="btn-group w-100 mb-3" role="group">
|
<span class="input-group-text bg-white"><i class="bi bi-person"></i></span>
|
||||||
<input type="radio" class="btn-check" name="order_type" id="ot-takeaway" value="takeaway" <?= $order_type === 'takeaway' ? 'checked' : '' ?>>
|
<input type="text" id="customer-search" class="form-control" placeholder="Search customer (Name/Phone)..." autocomplete="off">
|
||||||
<label class="btn btn-outline-primary btn-sm" for="ot-takeaway">Takeaway</label>
|
<button class="btn btn-outline-secondary" type="button" data-bs-toggle="modal" data-bs-target="#addCustomerModal" title="Add New Customer">
|
||||||
|
<i class="bi bi-person-plus"></i>
|
||||||
|
</button>
|
||||||
|
<div id="customer-dropdown" class="list-group shadow-sm position-absolute w-100" style="top: 100%; z-index: 1050; display: none; max-height: 200px; overflow-y: auto;"></div>
|
||||||
|
</div>
|
||||||
|
<div id="selected-customer-display" class="d-none alert alert-info p-1 mb-0 mt-1 d-flex justify-content-between align-items-center" style="font-size: 0.8rem;">
|
||||||
|
<span><i class="bi bi-person-check-fill me-1"></i> <strong id="selected-customer-name"></strong></span>
|
||||||
|
<button type="button" class="btn-close" style="font-size: 0.6rem;" onclick="clearSelectedCustomer()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="order_type" id="ot-dine-in" value="dine-in" <?= $order_type === 'dine-in' ? 'checked' : '' ?>>
|
<!-- Cart Items -->
|
||||||
<label class="btn btn-outline-primary btn-sm" for="ot-dine-in">Dine-In</label>
|
<div class="scrollable-y flex-grow-1 p-3" id="cart-items">
|
||||||
|
<div class="text-center text-muted mt-5 py-5">
|
||||||
|
<i class="bi bi-cart3 fs-1 opacity-25"></i>
|
||||||
|
<p class="mt-2">Cart is empty</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="order_type" id="ot-delivery" value="delivery" <?= $order_type === 'delivery' ? 'checked' : '' ?>>
|
<!-- Cart Summary & Actions -->
|
||||||
<label class="btn btn-outline-primary btn-sm" for="ot-delivery">Delivery</label>
|
<div class="p-3 border-top bg-white shadow-lg">
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span class="text-muted">Subtotal</span>
|
||||||
|
<span id="cart-subtotal">$0.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<span class="text-muted">VAT (<?= ($settings['vat_rate'] ?? 0) * 100 ?>%)</span>
|
||||||
|
<span id="cart-tax">$0.00</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mb-3 pt-2 border-top">
|
||||||
|
<h5 class="fw-bold mb-0">Total</h5>
|
||||||
|
<h5 class="fw-bold mb-0 text-primary" id="cart-total">$0.00</h5>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Customer Search -->
|
<div class="row g-2">
|
||||||
<div class="position-relative">
|
<div class="col-6">
|
||||||
<div class="input-group">
|
<button class="btn btn-outline-danger w-100 py-3 fw-bold" onclick="clearCart()">
|
||||||
<span class="input-group-text bg-white border-end-0"><i class="bi bi-person"></i></span>
|
<i class="bi bi-trash"></i> CLEAR
|
||||||
<input type="text" class="form-control border-start-0 ps-0" id="customer-search" placeholder="Search Customer..." autocomplete="off">
|
|
||||||
<button class="btn btn-outline-secondary d-none" type="button" id="clear-customer"><i class="bi bi-x"></i></button>
|
|
||||||
<button class="btn btn-outline-primary" type="button" id="add-customer-btn" title="Add New Customer">
|
|
||||||
<i class="bi bi-plus-lg"></i>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-group shadow-sm search-dropdown" id="customer-results"></div>
|
<div class="col-6">
|
||||||
<input type="hidden" id="selected-customer-id">
|
<button class="btn btn-primary w-100 py-3 fw-bold" onclick="proceedToPayment()">
|
||||||
|
PAYMENT <i class="bi bi-arrow-right"></i>
|
||||||
<div id="customer-info" class="small text-success mt-1 d-none">
|
</button>
|
||||||
<i class="bi bi-check-circle-fill me-1"></i> <span id="customer-name-display"></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Loyalty Section (Hidden if disabled) -->
|
|
||||||
<?php if ($loyalty_settings['is_enabled']): ?>
|
|
||||||
<div id="loyalty-section" class="d-none mt-2 p-2 bg-warning-subtle rounded border border-warning">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<span class="d-block fw-bold small text-warning-emphasis">Loyalty Points</span>
|
|
||||||
<span id="loyalty-points-display" class="fw-bold fs-5">0</span>
|
|
||||||
</div>
|
|
||||||
<button id="redeem-loyalty-btn" class="btn btn-sm btn-success shadow-sm" disabled>
|
|
||||||
<i class="bi bi-gift-fill me-1"></i> Redeem Meal
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="loyalty-message" class="small text-muted mt-1 fst-italic" style="font-size: 0.75rem;"></div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cart Items (Flex Grow) -->
|
|
||||||
<div class="flex-grow-1 overflow-auto p-3 bg-white" id="cart-items">
|
|
||||||
<div class="text-center text-muted mt-5">
|
|
||||||
<i class="bi bi-basket3 fs-1 text-light"></i>
|
|
||||||
<p class="mt-2">Cart is empty</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bottom: Totals & Action -->
|
|
||||||
<div class="p-3 border-top bg-light">
|
|
||||||
<div class="d-flex justify-content-between mb-2">
|
|
||||||
<span class="text-muted">Subtotal</span>
|
|
||||||
<span class="fw-bold" id="cart-subtotal"><?= format_currency(0) ?></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Discount Field -->
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
||||||
<span class="text-muted">Discount</span>
|
|
||||||
<div class="input-group input-group-sm w-50">
|
|
||||||
<span class="input-group-text bg-white border-end-0 text-muted">-</span>
|
|
||||||
<input type="number" id="cart-discount-input" class="form-control border-start-0 text-end" value="0" min="0" step="0.01">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between mb-3">
|
|
||||||
<span class="fs-5 fw-bold">Total</span>
|
|
||||||
<span class="fs-4 fw-bold text-primary" id="cart-total-price"><?= format_currency(0) ?></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if (has_permission('pos_add')): ?>
|
|
||||||
<div class="d-flex gap-2 w-100">
|
|
||||||
<button class="btn btn-primary w-50 btn-lg shadow-sm" id="quick-order-btn" disabled>
|
|
||||||
Quick Order <i class="bi bi-lightning-fill ms-2"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-warning w-50 btn-lg shadow-sm text-white" id="place-order-btn" disabled>
|
|
||||||
Place Order <i class="bi bi-clock ms-2"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<div class="alert alert-warning small py-2 mb-0 text-center">
|
|
||||||
<i class="bi bi-info-circle me-1"></i> View Only Mode
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="text-center mt-3 text-muted small" style="font-size: 0.7rem;">
|
|
||||||
Powered By Abidarcafe @2026
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -449,12 +411,43 @@ if (!$loyalty_settings) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Rating QR Modal -->
|
||||||
|
<div class="modal fade" id="ratingQRModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-warning">
|
||||||
|
<h5 class="modal-title fw-bold text-dark"><i class="bi bi-star-fill me-2"></i> Customer Rating QR</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body text-center py-4">
|
||||||
|
<p class="text-muted mb-4">Show this QR code to the customer to rate our staff and service.</p>
|
||||||
|
<div class="mb-4">
|
||||||
|
<img id="rating-qr-img" src="" alt="Rating QR Code" class="img-fluid border p-3 bg-white shadow-sm" style="max-width: 250px;">
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-light border small text-break" id="rating-url-text"></div>
|
||||||
|
<button class="btn btn-primary w-100 mt-3" onclick="window.print()">
|
||||||
|
<i class="bi bi-printer me-2"></i> Print QR Code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const COMPANY_SETTINGS = <?= json_encode($settings) ?>;
|
const COMPANY_SETTINGS = <?= json_encode($settings) ?>;
|
||||||
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
|
const LOYALTY_SETTINGS = <?= json_encode($loyalty_settings) ?>;
|
||||||
const PRODUCT_VARIANTS = <?= json_encode($variants_by_product) ?>;
|
const PRODUCT_VARIANTS = <?= json_encode($variants_by_product) ?>;
|
||||||
const PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
|
const PAYMENT_TYPES = <?= json_encode($payment_types) ?>;
|
||||||
const CURRENT_OUTLET = <?= json_encode(['id' => $outlet_id, 'name' => $current_outlet_name]) ?>;
|
const CURRENT_OUTLET = <?= json_encode(['id' => $outlet_id, 'name' => $current_outlet_name]) ?>;
|
||||||
|
const BASE_URL = '<?= get_base_url() ?>';
|
||||||
|
|
||||||
|
function showRatingQR() {
|
||||||
|
const ratingUrl = BASE_URL + 'rate.php';
|
||||||
|
document.getElementById('rating-qr-img').src = 'https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=' + encodeURIComponent(ratingUrl);
|
||||||
|
document.getElementById('rating-url-text').textContent = ratingUrl;
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('ratingQRModal'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="assets/js/main.js?v=<?= time() ?>"></script>
|
<script src="assets/js/main.js?v=<?= time() ?>"></script>
|
||||||
|
|||||||
375
rate.php
Normal file
375
rate.php
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
<?php
|
||||||
|
require_once 'db/config.php';
|
||||||
|
require_once 'includes/functions.php';
|
||||||
|
|
||||||
|
$companySettings = get_company_settings();
|
||||||
|
$companyName = $companySettings['company_name'] ?? 'Foody';
|
||||||
|
$logoUrl = $companySettings['logo_url'] ?? '';
|
||||||
|
|
||||||
|
// Handle rating submission
|
||||||
|
$success = false;
|
||||||
|
$error = '';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
$ratingType = $_POST['rating_type'] ?? 'staff'; // 'staff' or 'service'
|
||||||
|
$userId = $_POST['user_id'] ?? null;
|
||||||
|
$rating = $_POST['rating'] ?? null;
|
||||||
|
$comment = $_POST['comment'] ?? '';
|
||||||
|
|
||||||
|
if ($rating) {
|
||||||
|
try {
|
||||||
|
$pdo = db();
|
||||||
|
if ($ratingType === 'service') {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO service_ratings (rating, comment) VALUES (?, ?)");
|
||||||
|
$stmt->execute([$rating, $comment]);
|
||||||
|
$success = true;
|
||||||
|
} else if ($userId) {
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO staff_ratings (user_id, rating, comment) VALUES (?, ?, ?)");
|
||||||
|
$stmt->execute([$userId, $rating, $comment]);
|
||||||
|
$success = true;
|
||||||
|
} else {
|
||||||
|
$error = "Please select a staff member.";
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$error = "Error saving rating: " . $e->getMessage();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$error = "Please provide a rating.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch active and ratable users with pictures
|
||||||
|
$pdo = db();
|
||||||
|
$stmt = $pdo->query("SELECT id, full_name, username, profile_pic FROM users WHERE is_active = 1 AND is_ratable = 1 ORDER BY full_name ASC");
|
||||||
|
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Rate Our Service - <?= htmlspecialchars($companyName) ?></title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--primary-color: #007bff;
|
||||||
|
--secondary-color: #6c757d;
|
||||||
|
--accent-color: #ffc107;
|
||||||
|
--bg-gradient: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background: var(--bg-gradient);
|
||||||
|
min-height: 100vh;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 40px auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
max-height: 60px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-card, .service-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #f0f7ff 100%);
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-card:hover, .service-card:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
box-shadow: 0 15px 30px rgba(0,0,0,0.1);
|
||||||
|
border: 2px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-pic {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background: #eee;
|
||||||
|
border: 3px solid white;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-icon {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-name, .service-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rating {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rating input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rating label {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
color: #ddd;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-rating label:hover,
|
||||||
|
.star-rating label:hover ~ label,
|
||||||
|
.star-rating input:checked ~ label {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
|
background: var(--primary-color);
|
||||||
|
border: none;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: white;
|
||||||
|
transition: all 0.3s;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
#rating-form {
|
||||||
|
display: none;
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 20px;
|
||||||
|
box-shadow: 0 15px 40px rgba(0,0,0,0.1);
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-animation {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 5rem;
|
||||||
|
color: #28a745;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Decorative shapes */
|
||||||
|
.shape {
|
||||||
|
position: fixed;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.shape-1 { top: 10%; left: 5%; width: 100px; height: 100px; background: #007bff22; border-radius: 30% 70% 70% 30% / 30% 30% 70% 70%; }
|
||||||
|
.shape-2 { bottom: 10%; right: 5%; width: 150px; height: 150px; background: #ffc10711; border-radius: 50%; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="shape shape-1"></div>
|
||||||
|
<div class="shape shape-2"></div>
|
||||||
|
|
||||||
|
<div class="container rating-container">
|
||||||
|
<div class="header">
|
||||||
|
<?php if ($logoUrl): ?>
|
||||||
|
<img src="<?= htmlspecialchars($logoUrl) ?>" alt="Logo" class="logo">
|
||||||
|
<?php else: ?>
|
||||||
|
<h1 class="fw-bold"><?= htmlspecialchars($companyName) ?></h1>
|
||||||
|
<?php endif; ?>
|
||||||
|
<p class="text-muted" id="main-instruction">We value your feedback! What would you like to rate?</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($success): ?>
|
||||||
|
<div class="success-animation card border-0 shadow-sm rounded-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-check-circle-fill success-icon"></i>
|
||||||
|
<h2>Thank You!</h2>
|
||||||
|
<p>Your rating has been submitted successfully.</p>
|
||||||
|
<a href="index.php" class="btn btn-outline-primary rounded-pill px-4 mt-3">Back to Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php if ($error): ?>
|
||||||
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<?= htmlspecialchars($error) ?>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="row g-4" id="staff-list">
|
||||||
|
<!-- Rate Us Card -->
|
||||||
|
<div class="col-12 col-md-6 offset-md-3 mb-3">
|
||||||
|
<div class="service-card" onclick="selectService()">
|
||||||
|
<div class="service-icon">
|
||||||
|
<i class="bi bi-shop"></i>
|
||||||
|
</div>
|
||||||
|
<div class="service-name fs-4">Rate Our Services</div>
|
||||||
|
<p class="text-muted small">How was your overall experience with us?</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 text-center mt-5 mb-2">
|
||||||
|
<h5 class="fw-bold text-muted">OR RATE OUR STAFF</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (empty($users)): ?>
|
||||||
|
<div class="col-12 text-center">
|
||||||
|
<p class="text-muted">No staff members are currently available for rating.</p>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php foreach ($users as $user): ?>
|
||||||
|
<div class="col-6 col-md-3">
|
||||||
|
<div class="staff-card" onclick="selectStaff(<?= $user['id'] ?>, '<?= htmlspecialchars($user['full_name']) ?>', this)">
|
||||||
|
<?php if ($user['profile_pic']): ?>
|
||||||
|
<img src="<?= htmlspecialchars($user['profile_pic']) ?>" alt="<?= htmlspecialchars($user['full_name']) ?>" class="staff-pic">
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="staff-pic d-flex align-items-center justify-content-center bg-primary text-white fs-2 fw-bold">
|
||||||
|
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="staff-name"><?= htmlspecialchars($user['full_name'] ?: $user['username']) ?></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="rating-form" method="POST">
|
||||||
|
<input type="hidden" name="rating_type" id="rating-type" value="staff">
|
||||||
|
<input type="hidden" name="user_id" id="selected-user-id">
|
||||||
|
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<h4 id="rating-title" class="fw-bold mb-0">Rate <span id="staff-display-name"></span></h4>
|
||||||
|
<p id="rating-subtitle" class="text-muted"></p>
|
||||||
|
|
||||||
|
<div class="star-rating">
|
||||||
|
<input type="radio" id="star5" name="rating" value="5" required /><label for="star5" title="5 stars"><i class="bi bi-star-fill"></i></label>
|
||||||
|
<input type="radio" id="star4" name="rating" value="4" /><label for="star4" title="4 stars"><i class="bi bi-star-fill"></i></label>
|
||||||
|
<input type="radio" id="star3" name="rating" value="3" /><label for="star3" title="3 stars"><i class="bi bi-star-fill"></i></label>
|
||||||
|
<input type="radio" id="star2" name="rating" value="2" /><label for="star2" title="2 stars"><i class="bi bi-star-fill"></i></label>
|
||||||
|
<input type="radio" id="star1" name="rating" value="1" /><label for="star1" title="1 star"><i class="bi bi-star-fill"></i></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="comment" class="form-label fw-semibold" id="comment-label">Any additional comments?</label>
|
||||||
|
<textarea class="form-control rounded-4 shadow-sm border-0 bg-light" name="comment" id="comment" rows="3" placeholder="Tell us about your experience..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<button type="button" class="btn btn-light w-100 rounded-pill py-3 fw-bold" onclick="cancelSelection()">Cancel</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<button type="submit" class="btn btn-primary btn-submit m-0 py-3 fw-bold">Submit Rating</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function selectService() {
|
||||||
|
// Hide staff list
|
||||||
|
document.getElementById('staff-list').style.display = 'none';
|
||||||
|
document.getElementById('main-instruction').style.display = 'none';
|
||||||
|
|
||||||
|
// Set type to service
|
||||||
|
document.getElementById('rating-type').value = 'service';
|
||||||
|
document.getElementById('selected-user-id').value = '';
|
||||||
|
|
||||||
|
// Set labels
|
||||||
|
document.getElementById('staff-display-name').innerText = 'Our Services';
|
||||||
|
document.getElementById('rating-subtitle').innerText = 'We would love to hear how we are doing overall.';
|
||||||
|
document.getElementById('comment-label').innerText = 'What can we improve?';
|
||||||
|
|
||||||
|
// Show rating form
|
||||||
|
document.getElementById('rating-form').style.display = 'block';
|
||||||
|
|
||||||
|
// Smooth scroll to form
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectStaff(id, name, element) {
|
||||||
|
// Hide staff list
|
||||||
|
document.getElementById('staff-list').style.display = 'none';
|
||||||
|
document.getElementById('main-instruction').style.display = 'none';
|
||||||
|
|
||||||
|
// Set type to staff
|
||||||
|
document.getElementById('rating-type').value = 'staff';
|
||||||
|
document.getElementById('selected-user-id').value = id;
|
||||||
|
|
||||||
|
// Set labels
|
||||||
|
document.getElementById('staff-display-name').innerText = name;
|
||||||
|
document.getElementById('rating-subtitle').innerText = 'Rate the service provided by this staff member.';
|
||||||
|
document.getElementById('comment-label').innerText = 'Any specific feedback for ' + name + '?';
|
||||||
|
|
||||||
|
// Show rating form
|
||||||
|
document.getElementById('rating-form').style.display = 'block';
|
||||||
|
|
||||||
|
// Smooth scroll to form
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelSelection() {
|
||||||
|
document.getElementById('rating-form').style.display = 'none';
|
||||||
|
document.getElementById('staff-list').style.display = 'flex';
|
||||||
|
document.getElementById('main-instruction').style.display = 'block';
|
||||||
|
|
||||||
|
// Clear form
|
||||||
|
document.getElementById('comment').value = '';
|
||||||
|
const radios = document.getElementsByName('rating');
|
||||||
|
for(let i=0; i<radios.length; i++) radios[i].checked = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user