Customer Feedback SystemIN HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

Introduction to the Project

The Customer Feedback System is a comprehensive, full-stack web application designed to help businesses collect, manage, analyze, and respond to customer feedback effectively. This system provides a complete solution for gathering insights through multiple channels, understanding customer sentiment, and making data-driven decisions to improve products and services.

The application features role-based access control with four user types: Admin, Manager, Analyst, and Customer. Customers can submit feedback through various forms, while businesses can track satisfaction metrics, generate reports, and respond to feedback in real-time. The system includes advanced analytics, sentiment analysis, and customizable feedback forms.

Key Features

Core Features

  • Multi-channel Feedback Collection: Web forms, email surveys, QR codes, and API integration
  • Customizable Feedback Forms: Create different form templates with various question types
  • Rating Systems: Star ratings, NPS (Net Promoter Score), CSAT (Customer Satisfaction), and CES (Customer Effort Score)
  • Sentiment Analysis: Automatic detection of positive, neutral, or negative feedback
  • Real-time Dashboard: Live feedback stream with key metrics and visualizations
  • Feedback Management: View, filter, search, and manage all feedback submissions
  • Response Management: Acknowledge, respond to, and resolve customer feedback
  • Reporting & Analytics: Generate detailed reports with charts and insights
  • Category Management: Organize feedback by department, product, or issue type
  • Customer Profiles: View feedback history for individual customers

Advanced Features

  • NPS Tracking: Calculate and track Net Promoter Score over time
  • Trend Analysis: Identify patterns and trends in customer feedback
  • Tagging System: Add custom tags to feedback for better organization
  • Priority Levels: Assign priority to feedback items (Low, Medium, High, Critical)
  • Assignment System: Assign feedback to specific team members
  • Email Notifications: Alert managers and customers about feedback updates
  • Export Functionality: Export feedback data to CSV, Excel, or PDF
  • API Integration: RESTful API for integrating with other systems
  • Feedback Workflow: Custom workflows for feedback processing
  • Multi-language Support: Interface in multiple languages
  • Feedback Templates: Pre-built templates for common feedback scenarios

User Roles

Admin:

  • Full system access and configuration
  • User management (create, edit, delete users)
  • Role and permission management
  • System settings configuration
  • View all feedback and analytics
  • Manage feedback categories and tags
  • Export system-wide data

Manager:

  • View team performance metrics
  • Assign feedback to team members
  • Generate department-specific reports
  • Respond to critical feedback
  • Monitor team response times
  • View analytics dashboards

Analyst:

  • Access to all feedback data
  • Generate and export reports
  • Analyze trends and patterns
  • Create custom dashboards
  • Export data for further analysis
  • No user management capabilities

Customer:

  • Submit feedback through forms
  • View status of submitted feedback
  • Receive responses from the company
  • Track resolution progress
  • Provide additional information when requested

Technology Stack

  • Frontend: HTML5, CSS3, JavaScript (ES6+), Bootstrap 5
  • Backend: PHP 8.0+ (Core PHP with OOP MVC pattern)
  • Database: MySQL 5.7+
  • Additional Libraries:
  • Bootstrap 5 for responsive UI
  • Font Awesome for icons
  • Chart.js for data visualization
  • DataTables for advanced tables
  • Select2 for enhanced dropdowns
  • Moment.js for date handling
  • PHPMailer for email notifications
  • TCPDF for PDF generation
  • PhpSpreadsheet for Excel export
  • Sentiment PHP for basic sentiment analysis

Project File Structure

feedback-system/
│
├── assets/
│   ├── css/
│   │   ├── style.css
│   │   ├── dashboard.css
│   │   ├── forms.css
│   │   └── responsive.css
│   ├── js/
│   │   ├── main.js
│   │   ├── dashboard.js
│   │   ├── charts.js
│   │   ├── feedback.js
│   │   ├── forms.js
│   │   └── validation.js
│   ├── images/
│   │   └── avatars/
│   └── plugins/
│       ├── chart.js/
│       ├── datatables/
│       └── select2/
│
├── includes/
│   ├── config.php
│   ├── Database.php
│   ├── functions.php
│   ├── auth.php
│   ├── Feedback.php
│   ├── Category.php
│   ├── Tag.php
│   ├── User.php
│   ├── Report.php
│   ├── Notification.php
│   ├── Sentiment.php
│   └── helpers/
│       ├── EmailHelper.php
│       ├── ExportHelper.php
│       └── ValidationHelper.php
│
├── admin/
│   ├── dashboard.php
│   ├── users.php
│   ├── add_user.php
│   ├── edit_user.php
│   ├── roles.php
│   ├── categories.php
│   ├── tags.php
│   ├── settings.php
│   ├── feedback.php
│   ├── feedback_details.php
│   └── reports.php
│
├── manager/
│   ├── dashboard.php
│   ├── feedback.php
│   ├── feedback_details.php
│   ├── assign_feedback.php
│   ├── team_performance.php
│   ├── reports.php
│   └── settings.php
│
├── analyst/
│   ├── dashboard.php
│   ├── analytics.php
│   ├── trends.php
│   ├── sentiment.php
│   ├── reports.php
│   ├── export.php
│   └── custom_reports.php
│
├── customer/
│   ├── submit.php
│   ├── thanks.php
│   ├── status.php
│   ├── feedback_details.php
│   └── history.php
│
├── api/
│   ├── submit_feedback.php
│   ├── get_feedback.php
│   ├── update_status.php
│   ├── add_response.php
│   ├── get_chart_data.php
│   └── search.php
│
├── uploads/
│   └── attachments/
│
├── vendor/
│
├── index.php
├── login.php
├── register.php
├── forgot_password.php
├── reset_password.php
├── logout.php
├── .env
├── .gitignore
├── composer.json
└── sql/
└── database.sql

Database Schema

File: sql/database.sql

-- Create Database
CREATE DATABASE IF NOT EXISTS `feedback_system`;
USE `feedback_system`;
-- Users Table
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) UNIQUE NOT NULL,
`email` VARCHAR(100) UNIQUE NOT NULL,
`password` VARCHAR(255) NOT NULL,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
`avatar` VARCHAR(255) DEFAULT 'default.jpg',
`role` ENUM('admin', 'manager', 'analyst', 'customer') DEFAULT 'customer',
`department` VARCHAR(100),
`position` VARCHAR(100),
`phone` VARCHAR(20),
`email_verified` BOOLEAN DEFAULT FALSE,
`is_active` BOOLEAN DEFAULT TRUE,
`last_login` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_email` (`email`),
INDEX `idx_role` (`role`),
INDEX `idx_active` (`is_active`)
);
-- Feedback Categories Table
CREATE TABLE `categories` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`color` VARCHAR(7) DEFAULT '#6c757d',
`icon` VARCHAR(50) DEFAULT 'fa-folder',
`parent_id` INT(11),
`is_active` BOOLEAN DEFAULT TRUE,
`sort_order` INT DEFAULT 0,
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`parent_id`) REFERENCES `categories`(`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_active` (`is_active`)
);
-- Tags Table
CREATE TABLE `tags` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) UNIQUE NOT NULL,
`color` VARCHAR(7) DEFAULT '#6c757d',
`usage_count` INT DEFAULT 0,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_name` (`name`)
);
-- Feedback Forms Table
CREATE TABLE `feedback_forms` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`form_code` VARCHAR(50) UNIQUE NOT NULL,
`fields` JSON NOT NULL,
`type` ENUM('general', 'nps', 'csat', 'ces', 'custom') DEFAULT 'general',
`redirect_url` VARCHAR(255),
`email_notifications` BOOLEAN DEFAULT TRUE,
`notify_emails` TEXT,
`is_active` BOOLEAN DEFAULT TRUE,
`public_access` BOOLEAN DEFAULT TRUE,
`require_login` BOOLEAN DEFAULT FALSE,
`expiry_date` DATE,
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_code` (`form_code`),
INDEX `idx_active` (`is_active`)
);
-- Feedback Submissions Table
CREATE TABLE `feedback` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`feedback_id` VARCHAR(20) UNIQUE NOT NULL,
`form_id` INT(11),
`customer_id` INT(11),
`customer_email` VARCHAR(100),
`customer_name` VARCHAR(100),
`rating` INT,
`nps_score` INT,
`csat_score` INT,
`ces_score` INT,
`subject` VARCHAR(255),
`message` TEXT NOT NULL,
`sentiment` ENUM('positive', 'neutral', 'negative') DEFAULT 'neutral',
`sentiment_score` DECIMAL(5,2),
`priority` ENUM('low', 'medium', 'high', 'critical') DEFAULT 'medium',
`status` ENUM('new', 'in_progress', 'resolved', 'closed', 'spam') DEFAULT 'new',
`assigned_to` INT(11),
`assigned_at` DATETIME,
`source` VARCHAR(50) DEFAULT 'web',
`ip_address` VARCHAR(45),
`user_agent` TEXT,
`attachments` JSON,
`metadata` JSON,
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`resolved_at` DATETIME,
`resolution_time` INT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`form_id`) REFERENCES `feedback_forms`(`id`),
FOREIGN KEY (`customer_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`assigned_to`) REFERENCES `users`(`id`),
INDEX `idx_feedback_id` (`feedback_id`),
INDEX `idx_status` (`status`),
INDEX `idx_priority` (`priority`),
INDEX `idx_sentiment` (`sentiment`),
INDEX `idx_created` (`created_at`),
FULLTEXT INDEX `idx_search` (`subject`, `message`)
);
-- Feedback Responses Table
CREATE TABLE `feedback_responses` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`feedback_id` INT(11) NOT NULL,
`user_id` INT(11),
`response` TEXT NOT NULL,
`is_public` BOOLEAN DEFAULT FALSE,
`notify_customer` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`feedback_id`) REFERENCES `feedback`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_feedback` (`feedback_id`)
);
-- Feedback Tags Junction Table
CREATE TABLE `feedback_tags` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`feedback_id` INT(11) NOT NULL,
`tag_id` INT(11) NOT NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`feedback_id`) REFERENCES `feedback`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`tag_id`) REFERENCES `tags`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_tag` (`feedback_id`, `tag_id`)
);
-- Feedback Attachments Table
CREATE TABLE `feedback_attachments` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`feedback_id` INT(11) NOT NULL,
`filename` VARCHAR(255) NOT NULL,
`original_name` VARCHAR(255) NOT NULL,
`file_path` VARCHAR(255) NOT NULL,
`file_size` INT,
`file_type` VARCHAR(100),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`feedback_id`) REFERENCES `feedback`(`id`) ON DELETE CASCADE
);
-- Feedback History Table
CREATE TABLE `feedback_history` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`feedback_id` INT(11) NOT NULL,
`user_id` INT(11),
`action` VARCHAR(50) NOT NULL,
`old_value` TEXT,
`new_value` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`feedback_id`) REFERENCES `feedback`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_feedback` (`feedback_id`)
);
-- Notifications Table
CREATE TABLE `notifications` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`type` VARCHAR(50) NOT NULL,
`title` VARCHAR(255) NOT NULL,
`message` TEXT,
`link` VARCHAR(255),
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_user_read` (`user_id`, `is_read`)
);
-- Reports Table
CREATE TABLE `reports` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`type` VARCHAR(50) NOT NULL,
`parameters` JSON,
`file_path` VARCHAR(255),
`status` ENUM('pending', 'completed', 'failed') DEFAULT 'pending',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`completed_at` DATETIME,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
);
-- Activity Logs Table
CREATE TABLE `activity_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11),
`action` VARCHAR(100) NOT NULL,
`details` JSON,
`ip_address` VARCHAR(45),
`user_agent` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_user` (`user_id`),
INDEX `idx_created` (`created_at`)
);
-- System Settings Table
CREATE TABLE `system_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`setting_key` VARCHAR(100) UNIQUE NOT NULL,
`setting_value` TEXT,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Insert Default Admin
INSERT INTO `users` (`username`, `email`, `password`, `first_name`, `last_name`, `role`, `email_verified`, `is_active`) 
VALUES ('admin', '[email protected]', '$2y$10$YourHashedPasswordHere', 'System', 'Administrator', 'admin', TRUE, TRUE);
-- Insert Default Categories
INSERT INTO `categories` (`name`, `description`, `color`, `icon`, `sort_order`) VALUES
('Product Feedback', 'Feedback about products and features', '#4e73df', 'fa-box', 1),
('Customer Service', 'Feedback about customer service interactions', '#1cc88a', 'fa-headset', 2),
('Technical Issues', 'Bug reports and technical problems', '#e74a3b', 'fa-bug', 3),
('Billing', 'Feedback about billing and payments', '#f6c23e', 'fa-credit-card', 4),
('General', 'General feedback and suggestions', '#36b9cc', 'fa-comment', 5),
('Compliments', 'Positive feedback and compliments', '#858796', 'fa-smile', 6);
-- Insert Default Tags
INSERT INTO `tags` (`name`, `color`) VALUES
('urgent', '#e74a3b'),
('feature-request', '#4e73df'),
('bug', '#e74a3b'),
('question', '#f6c23e'),
('compliment', '#1cc88a'),
('complaint', '#e74a3b'),
('suggestion', '#36b9cc');
-- Insert Default System Settings
INSERT INTO `system_settings` (`setting_key`, `setting_value`, `description`) VALUES
('company_name', 'Feedback System', 'Company name'),
('company_email', '[email protected]', 'Company email'),
('company_phone', '+1-555-123-4567', 'Company phone'),
('timezone', 'America/New_York', 'Default timezone'),
('date_format', 'Y-m-d', 'Date format'),
('time_format', 'H:i', 'Time format'),
('items_per_page', '20', 'Items per page in tables'),
('enable_notifications', '1', 'Enable email notifications'),
('notification_email', '[email protected]', 'Notifications email'),
('auto_assign', '0', 'Auto-assign feedback to team members'),
('auto_response', '0', 'Send auto-response to customers'),
('auto_response_message', 'Thank you for your feedback. We will review it shortly.', 'Auto-response message'),
('sentiment_analysis', '1', 'Enable sentiment analysis'),
('nps_enabled', '1', 'Enable NPS surveys'),
('csat_enabled', '1', 'Enable CSAT surveys'),
('ces_enabled', '1', 'Enable CES surveys'),
('max_file_size', '5', 'Maximum file size in MB'),
('allowed_file_types', 'jpg,jpeg,png,gif,pdf,doc,docx', 'Allowed file types'),
('app_version', '1.0.0', 'Application version');

Core PHP Classes

Database Class

File: includes/Database.php

<?php
/**
* Database Class
* Handles all database connections and operations using PDO with singleton pattern
*/
class Database {
private static $instance = null;
private $connection;
private $statement;
private $host;
private $dbname;
private $username;
private $password;
/**
* Private constructor for singleton pattern
*/
private function __construct() {
$this->host = DB_HOST;
$this->dbname = DB_NAME;
$this->username = DB_USER;
$this->password = DB_PASS;
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
/**
* Get database instance (Singleton)
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Prepare and execute query with parameters
*/
public function query($sql, $params = []) {
try {
$this->statement = $this->connection->prepare($sql);
$this->statement->execute($params);
return $this->statement;
} catch (PDOException $e) {
$this->logError($e->getMessage(), $sql, $params);
throw new Exception("Database query failed: " . $e->getMessage());
}
}
/**
* Get single row
*/
public function getRow($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetch();
}
/**
* Get multiple rows
*/
public function getRows($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchAll();
}
/**
* Get single value
*/
public function getValue($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchColumn();
}
/**
* Insert data and return last insert ID
*/
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, $data);
return $this->connection->lastInsertId();
}
/**
* Update data
*/
public function update($table, $data, $where, $whereParams = []) {
$set = [];
foreach (array_keys($data) as $column) {
$set[] = "{$column} = :{$column}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE {$where}";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params)->rowCount();
}
/**
* Delete data
*/
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM {$table} WHERE {$where}";
return $this->query($sql, $params)->rowCount();
}
/**
* Begin transaction
*/
public function beginTransaction() {
return $this->connection->beginTransaction();
}
/**
* Commit transaction
*/
public function commit() {
return $this->connection->commit();
}
/**
* Rollback transaction
*/
public function rollback() {
return $this->connection->rollBack();
}
/**
* Get last insert ID
*/
public function lastInsertId() {
return $this->connection->lastInsertId();
}
/**
* Log database errors
*/
private function logError($message, $sql, $params) {
$logFile = __DIR__ . '/../logs/database.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] Error: {$message}\n";
$logMessage .= "SQL: {$sql}\n";
$logMessage .= "Params: " . json_encode($params) . "\n";
$logMessage .= "------------------------\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Prevent cloning of the instance
*/
private function __clone() {}
/**
* Prevent unserializing of the instance
*/
public function __wakeup() {}
}
?>

Configuration File

File: includes/config.php

<?php
/**
* Configuration File
* Loads environment variables and sets up constants
*/
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load environment variables from .env file
function loadEnv($path) {
if (!file_exists($path)) {
return false;
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if (!array_key_exists($name, $_ENV)) {
$_ENV[$name] = $value;
putenv(sprintf('%s=%s', $name, $value));
}
}
return true;
}
// Load environment variables
loadEnv(__DIR__ . '/../.env');
// Database Configuration
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_NAME', getenv('DB_NAME') ?: 'feedback_system');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application Configuration
define('APP_NAME', getenv('APP_NAME') ?: 'Customer Feedback System');
define('APP_URL', getenv('APP_URL') ?: 'http://localhost/feedback-system');
define('APP_VERSION', getenv('APP_VERSION') ?: '1.0.0');
define('DEBUG_MODE', getenv('DEBUG_MODE') === 'true');
// Security Configuration
define('SESSION_TIMEOUT', getenv('SESSION_TIMEOUT') ?: 3600); // 1 hour
define('BCRYPT_ROUNDS', 12);
define('CSRF_TOKEN_NAME', 'csrf_token');
// Upload Configuration
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
define('MAX_FILE_SIZE', getenv('MAX_FILE_SIZE') ?: 5 * 1024 * 1024); // 5MB
define('ALLOWED_FILE_TYPES', ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx']);
// Pagination
define('ITEMS_PER_PAGE', getenv('ITEMS_PER_PAGE') ?: 20);
// Date/Time Configuration
date_default_timezone_set(getenv('TIMEZONE') ?: 'America/New_York');
define('DATE_FORMAT', 'Y-m-d');
define('TIME_FORMAT', 'H:i');
define('DATETIME_FORMAT', 'Y-m-d H:i:s');
// Email Configuration
define('COMPANY_EMAIL', getenv('COMPANY_EMAIL') ?: '[email protected]');
define('COMPANY_NAME', getenv('COMPANY_NAME') ?: 'Feedback System');
define('ENABLE_NOTIFICATIONS', getenv('ENABLE_NOTIFICATIONS') === 'true');
// Feedback Settings
define('ENABLE_SENTIMENT', getenv('ENABLE_SENTIMENT') === 'true');
define('AUTO_ASSIGN', getenv('AUTO_ASSIGN') === 'true');
define('AUTO_RESPONSE', getenv('AUTO_RESPONSE') === 'true');
// Error Reporting
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Include required files
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/Feedback.php';
require_once __DIR__ . '/Category.php';
require_once __DIR__ . '/Tag.php';
require_once __DIR__ . '/User.php';
require_once __DIR__ . '/Report.php';
require_once __DIR__ . '/Notification.php';
require_once __DIR__ . '/Sentiment.php';
// Initialize database connection
$db = Database::getInstance();
// Load system settings
$settings = $db->getRows("SELECT setting_key, setting_value FROM system_settings");
foreach ($settings as $setting) {
define(strtoupper($setting['setting_key']), $setting['setting_value']);
}
// Set timezone for MySQL
$db->query("SET time_zone = ?", [date('P')]);
?>

Helper Functions

File: includes/functions.php

<?php
/**
* Helper Functions
* Common utility functions used throughout the application
*/
/**
* Sanitize input data
*/
function sanitize($input) {
if (is_array($input)) {
return array_map('sanitize', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
/**
* Generate CSRF token
*/
function generateCSRFToken() {
if (!isset($_SESSION[CSRF_TOKEN_NAME])) {
$_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
}
return $_SESSION[CSRF_TOKEN_NAME];
}
/**
* Verify CSRF token
*/
function verifyCSRFToken($token) {
if (!isset($_SESSION[CSRF_TOKEN_NAME]) || $token !== $_SESSION[CSRF_TOKEN_NAME]) {
return false;
}
return true;
}
/**
* Redirect to URL
*/
function redirect($url) {
header("Location: " . APP_URL . $url);
exit();
}
/**
* Format date for display
*/
function formatDate($date, $format = null) {
if ($format === null) {
$format = DATE_FORMAT . ' ' . TIME_FORMAT;
}
if ($date instanceof DateTime) {
return $date->format($format);
}
return date($format, strtotime($date));
}
/**
* Get time ago string
*/
function timeAgo($datetime) {
$time = strtotime($datetime);
$now = time();
$diff = $now - $time;
if ($diff < 60) {
return $diff . ' seconds ago';
} elseif ($diff < 3600) {
return floor($diff / 60) . ' minutes ago';
} elseif ($diff < 86400) {
return floor($diff / 3600) . ' hours ago';
} elseif ($diff < 2592000) {
return floor($diff / 86400) . ' days ago';
} elseif ($diff < 31536000) {
return floor($diff / 2592000) . ' months ago';
} else {
return floor($diff / 31536000) . ' years ago';
}
}
/**
* Generate unique feedback ID
*/
function generateFeedbackId() {
return 'FB' . date('Ymd') . strtoupper(uniqid());
}
/**
* Upload file
*/
function uploadFile($file, $targetDir, $allowedTypes = null) {
if ($allowedTypes === null) {
$allowedTypes = ALLOWED_FILE_TYPES;
}
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Upload failed with error code: ' . $file['error']];
}
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'error' => 'File size exceeds limit of ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedTypes)) {
return ['success' => false, 'error' => 'File type not allowed. Allowed types: ' . implode(', ', $allowedTypes)];
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . $extension;
$targetPath = $targetDir . '/' . $filename;
// Create directory if not exists
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Upload file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return [
'success' => true,
'filename' => $filename,
'original_name' => $file['name'],
'path' => $targetPath,
'size' => $file['size'],
'type' => $file['type']
];
}
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
/**
* Get status badge HTML
*/
function getStatusBadge($status) {
$badges = [
'new' => 'bg-primary',
'in_progress' => 'bg-warning',
'resolved' => 'bg-success',
'closed' => 'bg-secondary',
'spam' => 'bg-danger'
];
$class = $badges[$status] ?? 'bg-secondary';
$label = ucfirst(str_replace('_', ' ', $status));
return '<span class="badge ' . $class . '">' . $label . '</span>';
}
/**
* Get priority badge HTML
*/
function getPriorityBadge($priority) {
$badges = [
'low' => 'bg-info',
'medium' => 'bg-warning',
'high' => 'bg-danger',
'critical' => 'bg-dark'
];
$class = $badges[$priority] ?? 'bg-secondary';
$label = ucfirst($priority);
return '<span class="badge ' . $class . '">' . $label . '</span>';
}
/**
* Get sentiment badge HTML
*/
function getSentimentBadge($sentiment) {
$badges = [
'positive' => 'bg-success',
'neutral' => 'bg-info',
'negative' => 'bg-danger'
];
$class = $badges[$sentiment] ?? 'bg-secondary';
$label = ucfirst($sentiment);
return '<span class="badge ' . $class . '">' . $label . '</span>';
}
/**
* Calculate resolution time in hours
*/
function calculateResolutionTime($created_at, $resolved_at) {
$created = strtotime($created_at);
$resolved = strtotime($resolved_at);
return round(($resolved - $created) / 3600, 1);
}
/**
* Log activity
*/
function logActivity($userId, $action, $details = []) {
$db = Database::getInstance();
$db->insert('activity_logs', [
'user_id' => $userId,
'action' => $action,
'details' => json_encode($details),
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? null,
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null
]);
}
/**
* Get user IP address
*/
function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
/**
* Generate random string
*/
function generateRandomString($length = 32) {
return bin2hex(random_bytes($length / 2));
}
/**
* Validate email
*/
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Get unread notifications count
*/
function getUnreadNotificationsCount($userId) {
$db = Database::getInstance();
return $db->getValue(
"SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0",
[$userId]
);
}
/**
* Get feedback statistics
*/
function getFeedbackStats($userId = null, $role = null) {
$db = Database::getInstance();
$stats = [];
// Total feedback
$sql = "SELECT COUNT(*) FROM feedback";
if ($role == 'manager' && $userId) {
$sql .= " WHERE assigned_to = " . $userId;
}
$stats['total'] = $db->getValue($sql);
// New feedback
$sql = "SELECT COUNT(*) FROM feedback WHERE status = 'new'";
if ($role == 'manager' && $userId) {
$sql .= " AND assigned_to = " . $userId;
}
$stats['new'] = $db->getValue($sql);
// In progress
$sql = "SELECT COUNT(*) FROM feedback WHERE status = 'in_progress'";
if ($role == 'manager' && $userId) {
$sql .= " AND assigned_to = " . $userId;
}
$stats['in_progress'] = $db->getValue($sql);
// Resolved
$sql = "SELECT COUNT(*) FROM feedback WHERE status = 'resolved'";
if ($role == 'manager' && $userId) {
$sql .= " AND assigned_to = " . $userId;
}
$stats['resolved'] = $db->getValue($sql);
// Average rating
$stats['avg_rating'] = round($db->getValue("SELECT AVG(rating) FROM feedback WHERE rating IS NOT NULL"), 1);
// NPS score
$stats['nps'] = $db->getValue("SELECT AVG(nps_score) FROM feedback WHERE nps_score IS NOT NULL");
return $stats;
}
/**
* Generate pagination
*/
function paginate($currentPage, $totalPages, $url) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation"><ul class="pagination">';
// Previous button
if ($currentPage > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '&page=' . ($currentPage - 1) . '">Previous</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Previous</span></li>';
}
// Page numbers
for ($i = 1; $i <= $totalPages; $i++) {
if ($i == $currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '&page=' . $i . '">' . $i . '</a></li>';
}
}
// Next button
if ($currentPage < $totalPages) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '&page=' . ($currentPage + 1) . '">Next</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Next</span></li>';
}
$html .= '</ul></nav>';
return $html;
}
/**
* Get file size in human readable format
*/
function humanFileSize($bytes, $decimals = 2) {
$size = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . ' ' . $size[$factor];
}
?>

Authentication Class

File: includes/auth.php

<?php
/**
* Authentication Class
* Handles user authentication, registration, and session management
*/
class Auth {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Register new user
*/
public function register($data) {
try {
// Check if username or email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE username = ? OR email = ?",
[$data['username'], $data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Username or email already exists'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Insert user
$userId = $this->db->insert('users', [
'username' => $data['username'],
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'role' => 'customer'
]);
if ($userId) {
logActivity($userId, 'register', ['email' => $data['email']]);
return [
'success' => true,
'user_id' => $userId,
'message' => 'Registration successful. You can now login.'
];
}
return ['success' => false, 'error' => 'Registration failed'];
} catch (Exception $e) {
logError('Registration error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Registration failed: ' . $e->getMessage()];
}
}
/**
* Login user
*/
public function login($login, $password, $remember = false) {
try {
// Get user by username or email
$user = $this->db->getRow(
"SELECT * FROM users WHERE (username = ? OR email = ?) AND is_active = 1",
[$login, $login]
);
if (!$user) {
return ['success' => false, 'error' => 'Invalid username/email or password'];
}
// Verify password
if (!password_verify($password, $user['password'])) {
return ['success' => false, 'error' => 'Invalid username/email or password'];
}
// Check if password needs rehash
if (password_needs_rehash($user['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS])) {
$newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $newHash], 'id = :id', ['id' => $user['id']]);
}
// Set session
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
// Update last login
$this->db->update(
'users',
['last_login' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $user['id']]
);
logActivity($user['id'], 'login');
return ['success' => true, 'user' => $user];
} catch (Exception $e) {
logError('Login error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Login failed'];
}
}
/**
* Logout user
*/
public function logout() {
if (isset($_SESSION['user_id'])) {
logActivity($_SESSION['user_id'], 'logout');
}
$_SESSION = array();
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
session_destroy();
}
/**
* Check if user is logged in
*/
public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
/**
* Get current user
*/
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
}
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$_SESSION['user_id']]
);
}
/**
* Check if user has role
*/
public function hasRole($role) {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
/**
* Check if user has any of the roles
*/
public function hasAnyRole($roles) {
if (!isset($_SESSION['user_role'])) {
return false;
}
return in_array($_SESSION['user_role'], $roles);
}
/**
* Require login
*/
public function requireLogin() {
if (!$this->isLoggedIn()) {
$_SESSION['error'] = 'Please login to access this page';
redirect('/login.php');
}
}
/**
* Require role
*/
public function requireRole($role) {
$this->requireLogin();
if (!$this->hasRole($role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
if ($this->hasRole('admin')) {
redirect('/admin/dashboard.php');
} elseif ($this->hasRole('manager')) {
redirect('/manager/dashboard.php');
} elseif ($this->hasRole('analyst')) {
redirect('/analyst/dashboard.php');
} else {
redirect('/customer/submit.php');
}
}
}
/**
* Forgot password
*/
public function forgotPassword($email) {
$user = $this->db->getRow(
"SELECT id, first_name FROM users WHERE email = ?",
[$email]
);
if ($user) {
$token = generateRandomString();
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$this->db->update(
'users',
['reset_token' => $token, 'reset_expires' => $expires],
'id = :id',
['id' => $user['id']]
);
// Send reset email (simplified)
$resetLink = APP_URL . "/reset_password.php?token=" . $token;
// In production, use PHPMailer
$subject = "Password Reset - " . APP_NAME;
$message = "Hello {$user['first_name']},\n\n";
$message .= "Click the link below to reset your password:\n";
$message .= $resetLink . "\n\n";
$message .= "This link will expire in 1 hour.\n\n";
$message .= "If you didn't request this, please ignore this email.\n";
mail($email, $subject, $message, "From: " . COMPANY_EMAIL);
return true;
}
return false;
}
/**
* Reset password
*/
public function resetPassword($token, $password) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()",
[$token]
);
if ($user) {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update(
'users',
['password' => $hashedPassword, 'reset_token' => null, 'reset_expires' => null],
'id = :id',
['id' => $user['id']]
);
logActivity($user['id'], 'reset_password');
return true;
}
return false;
}
/**
* Check session timeout
*/
public function checkSessionTimeout() {
if ($this->isLoggedIn() && isset($_SESSION['login_time'])) {
if (time() - $_SESSION['login_time'] > SESSION_TIMEOUT) {
$this->logout();
$_SESSION['error'] = 'Your session has expired. Please login again.';
redirect('/login.php');
}
}
}
}
// Initialize Auth
$auth = new Auth();
// Check session timeout
$auth->checkSessionTimeout();
?>

Feedback Class

File: includes/Feedback.php

<?php
/**
* Feedback Class
* Handles all feedback-related operations
*/
class Feedback {
private $db;
private $sentiment;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
$this->sentiment = new Sentiment();
}
/**
* Submit new feedback
*/
public function submit($data) {
try {
$this->db->beginTransaction();
// Generate feedback ID
$feedbackId = generateFeedbackId();
// Analyze sentiment if enabled
$sentiment = 'neutral';
$sentimentScore = null;
if (ENABLE_SENTIMENT && !empty($data['message'])) {
$sentimentResult = $this->sentiment->analyze($data['message']);
$sentiment = $sentimentResult['sentiment'];
$sentimentScore = $sentimentResult['score'];
}
// Handle attachments
$attachments = [];
if (!empty($_FILES['attachments'])) {
$uploadDir = UPLOAD_DIR . 'attachments/';
foreach ($_FILES['attachments']['tmp_name'] as $key => $tmp_name) {
if ($_FILES['attachments']['error'][$key] == 0) {
$file = [
'name' => $_FILES['attachments']['name'][$key],
'type' => $_FILES['attachments']['type'][$key],
'tmp_name' => $tmp_name,
'error' => $_FILES['attachments']['error'][$key],
'size' => $_FILES['attachments']['size'][$key]
];
$result = uploadFile($file, $uploadDir);
if ($result['success']) {
$attachments[] = [
'filename' => $result['filename'],
'original_name' => $result['original_name'],
'size' => $result['size'],
'type' => $result['type']
];
}
}
}
}
// Prepare feedback data
$feedbackData = [
'feedback_id' => $feedbackId,
'form_id' => $data['form_id'] ?? null,
'customer_id' => $data['customer_id'] ?? null,
'customer_email' => $data['customer_email'] ?? null,
'customer_name' => $data['customer_name'] ?? null,
'rating' => $data['rating'] ?? null,
'nps_score' => $data['nps_score'] ?? null,
'csat_score' => $data['csat_score'] ?? null,
'ces_score' => $data['ces_score'] ?? null,
'subject' => $data['subject'] ?? null,
'message' => $data['message'],
'sentiment' => $sentiment,
'sentiment_score' => $sentimentScore,
'priority' => $this->calculatePriority($data),
'source' => $data['source'] ?? 'web',
'ip_address' => getUserIP(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
'attachments' => !empty($attachments) ? json_encode($attachments) : null,
'metadata' => isset($data['metadata']) ? json_encode($data['metadata']) : null
];
// Insert feedback
$feedbackDbId = $this->db->insert('feedback', $feedbackData);
if ($feedbackDbId) {
// Add tags if provided
if (!empty($data['tags'])) {
foreach ($data['tags'] as $tagName) {
$tag = $this->getOrCreateTag($tagName);
$this->db->insert('feedback_tags', [
'feedback_id' => $feedbackDbId,
'tag_id' => $tag['id']
]);
}
}
// Auto-assign if enabled
if (AUTO_ASSIGN) {
$this->autoAssign($feedbackDbId);
}
// Send auto-response if enabled
if (AUTO_RESPONSE && !empty($data['customer_email'])) {
$this->sendAutoResponse($feedbackDbId);
}
// Notify managers
$this->notifyManagers($feedbackDbId);
// Log activity
logActivity($data['customer_id'] ?? null, 'submit_feedback', ['feedback_id' => $feedbackId]);
$this->db->commit();
return [
'success' => true,
'feedback_id' => $feedbackId,
'message' => 'Feedback submitted successfully'
];
}
$this->db->rollback();
return ['success' => false, 'error' => 'Failed to submit feedback'];
} catch (Exception $e) {
$this->db->rollback();
logError('Submit feedback error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to submit feedback: ' . $e->getMessage()];
}
}
/**
* Calculate priority based on data
*/
private function calculatePriority($data) {
// Default priority
$priority = 'medium';
// Check for high priority keywords
if (!empty($data['message'])) {
$urgentKeywords = ['urgent', 'emergency', 'critical', 'asap', 'immediately'];
$message = strtolower($data['message']);
foreach ($urgentKeywords as $keyword) {
if (strpos($message, $keyword) !== false) {
$priority = 'high';
break;
}
}
}
// Low rating = high priority
if (!empty($data['rating']) && $data['rating'] <= 2) {
$priority = 'high';
}
// Negative sentiment = high priority
if (!empty($data['sentiment']) && $data['sentiment'] == 'negative') {
$priority = 'high';
}
return $priority;
}
/**
* Get or create tag
*/
private function getOrCreateTag($tagName) {
$tag = $this->db->getRow("SELECT id FROM tags WHERE name = ?", [$tagName]);
if ($tag) {
$this->db->update('tags', ['usage_count' => $tag['usage_count'] + 1], 'id = :id', ['id' => $tag['id']]);
return $tag;
}
$tagId = $this->db->insert('tags', [
'name' => $tagName,
'usage_count' => 1
]);
return ['id' => $tagId];
}
/**
* Auto-assign feedback to team member
*/
private function autoAssign($feedbackId) {
// Get least busy team member
$teamMember = $this->db->getRow(
"SELECT u.id FROM users u
LEFT JOIN feedback f ON u.id = f.assigned_to AND f.status IN ('new', 'in_progress')
WHERE u.role IN ('manager', 'analyst') AND u.is_active = 1
GROUP BY u.id
ORDER BY COUNT(f.id) ASC
LIMIT 1"
);
if ($teamMember) {
$this->db->update(
'feedback',
['assigned_to' => $teamMember['id'], 'assigned_at' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $feedbackId]
);
// Notify team member
$notification = new Notification();
$notification->create(
$teamMember['id'],
'assignment',
'New Feedback Assigned',
'A new feedback item has been assigned to you.',
'/manager/feedback_details.php?id=' . $feedbackId
);
}
}
/**
* Send auto-response to customer
*/
private function sendAutoResponse($feedbackId) {
$feedback = $this->getFeedbackById($feedbackId);
if ($feedback && !empty($feedback['customer_email'])) {
$subject = "Thank you for your feedback - " . COMPANY_NAME;
$message = AUTO_RESPONSE_MESSAGE;
// In production, use PHPMailer with HTML template
mail($feedback['customer_email'], $subject, $message, "From: " . COMPANY_EMAIL);
}
}
/**
* Notify managers about new feedback
*/
private function notifyManagers($feedbackId) {
$managers = $this->db->getRows(
"SELECT id FROM users WHERE role IN ('admin', 'manager') AND is_active = 1"
);
$notification = new Notification();
foreach ($managers as $manager) {
$notification->create(
$manager['id'],
'new_feedback',
'New Feedback Received',
'New feedback has been submitted and requires attention.',
'/manager/feedback_details.php?id=' . $feedbackId
);
}
}
/**
* Get feedback by ID
*/
public function getFeedbackById($id) {
$sql = "SELECT f.*, 
u.first_name as assigned_first_name, u.last_name as assigned_last_name,
c.name as category_name,
GROUP_CONCAT(t.name) as tags
FROM feedback f
LEFT JOIN users u ON f.assigned_to = u.id
LEFT JOIN categories c ON f.category_id = c.id
LEFT JOIN feedback_tags ft ON f.id = ft.feedback_id
LEFT JOIN tags t ON ft.tag_id = t.id
WHERE f.id = ? OR f.feedback_id = ?
GROUP BY f.id";
return $this->db->getRow($sql, [$id, $id]);
}
/**
* Get feedback list with filters
*/
public function getFeedbackList($filters = [], $page = 1, $limit = null) {
if ($limit === null) {
$limit = ITEMS_PER_PAGE;
}
$offset = ($page - 1) * $limit;
$sql = "SELECT f.*, 
u.first_name as assigned_first_name, u.last_name as assigned_last_name,
c.name as category_name
FROM feedback f
LEFT JOIN users u ON f.assigned_to = u.id
LEFT JOIN categories c ON f.category_id = c.id
WHERE 1=1";
$params = [];
// Apply filters
if (!empty($filters['status'])) {
$sql .= " AND f.status = :status";
$params['status'] = $filters['status'];
}
if (!empty($filters['priority'])) {
$sql .= " AND f.priority = :priority";
$params['priority'] = $filters['priority'];
}
if (!empty($filters['sentiment'])) {
$sql .= " AND f.sentiment = :sentiment";
$params['sentiment'] = $filters['sentiment'];
}
if (!empty($filters['assigned_to'])) {
$sql .= " AND f.assigned_to = :assigned_to";
$params['assigned_to'] = $filters['assigned_to'];
}
if (!empty($filters['category_id'])) {
$sql .= " AND f.category_id = :category_id";
$params['category_id'] = $filters['category_id'];
}
if (!empty($filters['date_from'])) {
$sql .= " AND DATE(f.created_at) >= :date_from";
$params['date_from'] = $filters['date_from'];
}
if (!empty($filters['date_to'])) {
$sql .= " AND DATE(f.created_at) <= :date_to";
$params['date_to'] = $filters['date_to'];
}
if (!empty($filters['search'])) {
$sql .= " AND (f.subject LIKE :search OR f.message LIKE :search OR f.customer_name LIKE :search OR f.customer_email LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
// Filter by user role
if (!empty($filters['user_id']) && !empty($filters['user_role'])) {
if ($filters['user_role'] == 'manager') {
$sql .= " AND f.assigned_to = :user_id";
$params['user_id'] = $filters['user_id'];
} elseif ($filters['user_role'] == 'customer') {
$sql .= " AND f.customer_id = :user_id";
$params['user_id'] = $filters['user_id'];
}
}
$sql .= " ORDER BY f.created_at DESC LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Get feedback count
*/
public function getFeedbackCount($filters = []) {
$sql = "SELECT COUNT(*) FROM feedback WHERE 1=1";
$params = [];
// Apply same filters as above
if (!empty($filters['status'])) {
$sql .= " AND status = :status";
$params['status'] = $filters['status'];
}
if (!empty($filters['priority'])) {
$sql .= " AND priority = :priority";
$params['priority'] = $filters['priority'];
}
if (!empty($filters['assigned_to'])) {
$sql .= " AND assigned_to = :assigned_to";
$params['assigned_to'] = $filters['assigned_to'];
}
if (!empty($filters['user_id']) && !empty($filters['user_role'])) {
if ($filters['user_role'] == 'manager') {
$sql .= " AND assigned_to = :user_id";
$params['user_id'] = $filters['user_id'];
} elseif ($filters['user_role'] == 'customer') {
$sql .= " AND customer_id = :user_id";
$params['user_id'] = $filters['user_id'];
}
}
return $this->db->getValue($sql, $params);
}
/**
* Update feedback status
*/
public function updateStatus($feedbackId, $status, $userId) {
try {
$feedback = $this->getFeedbackById($feedbackId);
if (!$feedback) {
return ['success' => false, 'error' => 'Feedback not found'];
}
$oldStatus = $feedback['status'];
$updateData = ['status' => $status];
if ($status == 'resolved') {
$updateData['resolved_at'] = date('Y-m-d H:i:s');
$updateData['resolution_time'] = calculateResolutionTime($feedback['created_at'], date('Y-m-d H:i:s'));
}
$this->db->update('feedback', $updateData, 'id = :id', ['id' => $feedback['id']]);
// Log history
$this->addHistory($feedback['id'], $userId, 'status_change', $oldStatus, $status);
// Notify customer if email exists
if ($status == 'resolved' && !empty($feedback['customer_email'])) {
$this->sendResolutionEmail($feedback['id']);
}
logActivity($userId, 'update_feedback_status', [
'feedback_id' => $feedback['feedback_id'],
'old_status' => $oldStatus,
'new_status' => $status
]);
return ['success' => true, 'message' => 'Status updated successfully'];
} catch (Exception $e) {
logError('Update status error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update status'];
}
}
/**
* Assign feedback to user
*/
public function assignFeedback($feedbackId, $assignToId, $userId) {
try {
$feedback = $this->getFeedbackById($feedbackId);
if (!$feedback) {
return ['success' => false, 'error' => 'Feedback not found'];
}
$oldAssignee = $feedback['assigned_to'];
$this->db->update(
'feedback',
['assigned_to' => $assignToId, 'assigned_at' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $feedback['id']]
);
// Log history
$this->addHistory($feedback['id'], $userId, 'assignment', $oldAssignee, $assignToId);
// Notify new assignee
$notification = new Notification();
$notification->create(
$assignToId,
'assignment',
'Feedback Assigned to You',
'A feedback item has been assigned to you.',
'/manager/feedback_details.php?id=' . $feedback['id']
);
logActivity($userId, 'assign_feedback', [
'feedback_id' => $feedback['feedback_id'],
'assigned_to' => $assignToId
]);
return ['success' => true, 'message' => 'Feedback assigned successfully'];
} catch (Exception $e) {
logError('Assign feedback error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to assign feedback'];
}
}
/**
* Add response to feedback
*/
public function addResponse($feedbackId, $userId, $response, $isPublic = false, $notifyCustomer = true) {
try {
$feedback = $this->getFeedbackById($feedbackId);
if (!$feedback) {
return ['success' => false, 'error' => 'Feedback not found'];
}
$this->db->insert('feedback_responses', [
'feedback_id' => $feedback['id'],
'user_id' => $userId,
'response' => $response,
'is_public' => $isPublic,
'notify_customer' => $notifyCustomer
]);
// If feedback was new, change to in_progress
if ($feedback['status'] == 'new') {
$this->updateStatus($feedback['id'], 'in_progress', $userId);
}
// Notify customer if requested
if ($notifyCustomer && !empty($feedback['customer_email'])) {
$this->sendResponseEmail($feedback['id'], $response);
}
logActivity($userId, 'add_response', [
'feedback_id' => $feedback['feedback_id']
]);
return ['success' => true, 'message' => 'Response added successfully'];
} catch (Exception $e) {
logError('Add response error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to add response'];
}
}
/**
* Get feedback responses
*/
public function getResponses($feedbackId) {
return $this->db->getRows(
"SELECT r.*, u.first_name, u.last_name, u.avatar
FROM feedback_responses r
LEFT JOIN users u ON r.user_id = u.id
WHERE r.feedback_id = ?
ORDER BY r.created_at ASC",
[$feedbackId]
);
}
/**
* Add history entry
*/
private function addHistory($feedbackId, $userId, $action, $oldValue, $newValue) {
$this->db->insert('feedback_history', [
'feedback_id' => $feedbackId,
'user_id' => $userId,
'action' => $action,
'old_value' => $oldValue,
'new_value' => $newValue
]);
}
/**
* Get feedback history
*/
public function getHistory($feedbackId) {
return $this->db->getRows(
"SELECT h.*, u.first_name, u.last_name
FROM feedback_history h
LEFT JOIN users u ON h.user_id = u.id
WHERE h.feedback_id = ?
ORDER BY h.created_at DESC",
[$feedbackId]
);
}
/**
* Send resolution email to customer
*/
private function sendResolutionEmail($feedbackId) {
$feedback = $this->getFeedbackById($feedbackId);
if ($feedback && !empty($feedback['customer_email'])) {
$subject = "Your feedback has been resolved - " . COMPANY_NAME;
$message = "Dear " . ($feedback['customer_name'] ?? 'Customer') . ",\n\n";
$message .= "Your feedback (ID: " . $feedback['feedback_id'] . ") has been marked as resolved.\n";
$message .= "Thank you for helping us improve our services.\n\n";
$message .= "Best regards,\n" . COMPANY_NAME;
mail($feedback['customer_email'], $subject, $message, "From: " . COMPANY_EMAIL);
}
}
/**
* Send response email to customer
*/
private function sendResponseEmail($feedbackId, $response) {
$feedback = $this->getFeedbackById($feedbackId);
if ($feedback && !empty($feedback['customer_email'])) {
$subject = "Response to your feedback - " . COMPANY_NAME;
$message = "Dear " . ($feedback['customer_name'] ?? 'Customer') . ",\n\n";
$message .= "We have responded to your feedback (ID: " . $feedback['feedback_id'] . "):\n\n";
$message .= $response . "\n\n";
$message .= "You can view the response at: " . APP_URL . "/customer/feedback_details.php?id=" . $feedback['feedback_id'] . "\n\n";
$message .= "Best regards,\n" . COMPANY_NAME;
mail($feedback['customer_email'], $subject, $message, "From: " . COMPANY_EMAIL);
}
}
/**
* Get dashboard statistics
*/
public function getDashboardStats() {
$stats = [];
// Today's feedback
$stats['today'] = $this->db->getValue(
"SELECT COUNT(*) FROM feedback WHERE DATE(created_at) = CURDATE()"
);
// This week
$stats['week'] = $this->db->getValue(
"SELECT COUNT(*) FROM feedback WHERE YEARWEEK(created_at) = YEARWEEK(NOW())"
);
// This month
$stats['month'] = $this->db->getValue(
"SELECT COUNT(*) FROM feedback WHERE MONTH(created_at) = MONTH(NOW()) AND YEAR(created_at) = YEAR(NOW())"
);
// Average response time (in hours)
$stats['avg_response_time'] = round($this->db->getValue(
"SELECT AVG(TIMESTAMPDIFF(HOUR, created_at, resolved_at)) FROM feedback WHERE resolved_at IS NOT NULL"
) ?? 0, 1);
// Satisfaction scores
$stats['avg_rating'] = round($this->db->getValue("SELECT AVG(rating) FROM feedback WHERE rating IS NOT NULL"), 1);
$stats['avg_nps'] = round($this->db->getValue("SELECT AVG(nps_score) FROM feedback WHERE nps_score IS NOT NULL"), 1);
$stats['avg_csat'] = round($this->db->getValue("SELECT AVG(csat_score) FROM feedback WHERE csat_score IS NOT NULL"), 1);
$stats['avg_ces'] = round($this->db->getValue("SELECT AVG(ces_score) FROM feedback WHERE ces_score IS NOT NULL"), 1);
return $stats;
}
/**
* Get chart data
*/
public function getChartData($period = 'week') {
$data = [];
switch ($period) {
case 'week':
for ($i = 6; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
$data['labels'][] = date('D', strtotime($date));
$data['values'][] = $this->db->getValue(
"SELECT COUNT(*) FROM feedback WHERE DATE(created_at) = ?",
[$date]
);
}
break;
case 'month':
for ($i = 29; $i >= 0; $i--) {
$date = date('Y-m-d', strtotime("-$i days"));
if ($i % 5 == 0) {
$data['labels'][] = date('M d', strtotime($date));
} else {
$data['labels'][] = '';
}
$data['values'][] = $this->db->getValue(
"SELECT COUNT(*) FROM feedback WHERE DATE(created_at) = ?",
[$date]
);
}
break;
case 'year':
for ($i = 11; $i >= 0; $i--) {
$month = date('Y-m', strtotime("-$i months"));
$data['labels'][] = date('M Y', strtotime($month . '-01'));
$data['values'][] = $this->db->getValue(
"SELECT COUNT(*) FROM feedback WHERE DATE_FORMAT(created_at, '%Y-%m') = ?",
[$month]
);
}
break;
}
return $data;
}
}
?>

Sentiment Analysis Class

File: includes/Sentiment.php

<?php
/**
* Sentiment Analysis Class
* Basic sentiment analysis for feedback messages
*/
class Sentiment {
private $positiveWords = [];
private $negativeWords = [];
/**
* Constructor
*/
public function __construct() {
$this->loadWordLists();
}
/**
* Load positive and negative word lists
*/
private function loadWordLists() {
// Positive words
$this->positiveWords = [
'good', 'great', 'excellent', 'amazing', 'wonderful', 'fantastic',
'awesome', 'love', 'perfect', 'best', 'happy', 'satisfied',
'pleased', 'impressed', 'thank', 'thanks', 'appreciate',
'helpful', 'friendly', 'professional', 'quick', 'fast',
'easy', 'simple', 'clear', 'effective', 'quality',
'recommend', 'outstanding', 'superb', 'brilliant',
'exceptional', 'fabulous', 'terrific', 'superior'
];
// Negative words
$this->negativeWords = [
'bad', 'terrible', 'awful', 'horrible', 'poor', 'worst',
'hate', 'disappointed', 'disappointing', 'frustrating',
'annoying', 'angry', 'upset', 'mad', 'furious',
'useless', 'broken', 'waste', 'problem', 'issue',
'difficult', 'complicated', 'confusing', 'slow',
'expensive', 'overpriced', 'rude', 'unhelpful',
'unprofessional', 'delay', 'late', 'wrong',
'mistake', 'error', 'bug', 'crash', 'fail',
'failure', 'complaint', 'dissatisfied'
];
}
/**
* Analyze sentiment of text
*/
public function analyze($text) {
$text = strtolower($text);
$words = str_word_count($text, 1);
$positiveCount = 0;
$negativeCount = 0;
foreach ($words as $word) {
if (in_array($word, $this->positiveWords)) {
$positiveCount++;
}
if (in_array($word, $this->negativeWords)) {
$negativeCount++;
}
}
$total = $positiveCount + $negativeCount;
if ($total == 0) {
return [
'sentiment' => 'neutral',
'score' => 0,
'positive' => 0,
'negative' => 0
];
}
$score = ($positiveCount - $negativeCount) / $total;
if ($score > 0.2) {
$sentiment = 'positive';
} elseif ($score < -0.2) {
$sentiment = 'negative';
} else {
$sentiment = 'neutral';
}
return [
'sentiment' => $sentiment,
'score' => $score,
'positive' => $positiveCount,
'negative' => $negativeCount,
'total' => $total
];
}
/**
* Get detailed sentiment analysis
*/
public function getDetailedAnalysis($text) {
$result = $this->analyze($text);
$result['positive_words'] = $this->findWords($text, $this->positiveWords);
$result['negative_words'] = $this->findWords($text, $this->negativeWords);
return $result;
}
/**
* Find specific words in text
*/
private function findWords($text, $wordList) {
$found = [];
$text = strtolower($text);
foreach ($wordList as $word) {
if (strpos($text, $word) !== false) {
$found[] = $word;
}
}
return $found;
}
}
?>

Frontend Pages

Customer Feedback Form

File: customer/submit.php

<?php
require_once '../includes/config.php';
$formId = isset($_GET['form']) ? (int)$_GET['form'] : null;
$form = null;
if ($formId) {
$form = $db->getRow(
"SELECT * FROM feedback_forms WHERE id = ? AND is_active = 1 AND (expiry_date IS NULL OR expiry_date >= CURDATE())",
[$formId]
);
if ($form) {
$fields = json_decode($form['fields'], true);
}
}
// Check if user is logged in
$user = null;
if ($auth->isLoggedIn()) {
$user = $auth->getCurrentUser();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Submit Feedback - <?php echo COMPANY_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Star Rating CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/rateYo/2.3.2/jquery.rateyo.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="../assets/css/forms.css">
</head>
<body>
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8 col-lg-6">
<div class="card shadow-lg border-0">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-comment me-2"></i>
<?php echo $form ? htmlspecialchars($form['name']) : 'Submit Feedback'; ?>
</h3>
<?php if ($form && !empty($form['description'])): ?>
<p class="mb-0 mt-2 text-white-50"><?php echo htmlspecialchars($form['description']); ?></p>
<?php endif; ?>
</div>
<div class="card-body p-5">
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success alert-dismissible fade show">
<i class="fas fa-check-circle me-2"></i>
<?php echo $_SESSION['success']; unset($_SESSION['success']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $_SESSION['error']; unset($_SESSION['error']); ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form action="../api/submit_feedback.php" method="POST" enctype="multipart/form-data" onsubmit="return validateForm()">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<input type="hidden" name="form_id" value="<?php echo $formId; ?>">
<?php if (!$auth->isLoggedIn()): ?>
<!-- Customer Information -->
<div class="mb-4">
<h5 class="mb-3">Your Information</h5>
<div class="row">
<div class="col-md-6 mb-3">
<label for="name" class="form-label">
<i class="fas fa-user me-2"></i>Your Name
</label>
<input type="text" class="form-control" id="name" name="customer_name" 
placeholder="Enter your name">
</div>
<div class="col-md-6 mb-3">
<label for="email" class="form-label">
<i class="fas fa-envelope me-2"></i>Email Address
</label>
<input type="email" class="form-control" id="email" name="customer_email" 
placeholder="Enter your email">
</div>
</div>
</div>
<?php else: ?>
<input type="hidden" name="customer_id" value="<?php echo $user['id']; ?>">
<input type="hidden" name="customer_name" value="<?php echo $user['first_name'] . ' ' . $user['last_name']; ?>">
<input type="hidden" name="customer_email" value="<?php echo $user['email']; ?>">
<?php endif; ?>
<?php if (!$form || $form['type'] == 'general'): ?>
<!-- General Feedback -->
<div class="mb-4">
<h5 class="mb-3">Feedback Details</h5>
<div class="mb-3">
<label for="subject" class="form-label">
<i class="fas fa-heading me-2"></i>Subject
</label>
<input type="text" class="form-control" id="subject" name="subject" 
placeholder="Brief summary of your feedback" required>
</div>
<div class="mb-3">
<label for="category" class="form-label">
<i class="fas fa-tag me-2"></i>Category
</label>
<select class="form-select" id="category" name="category_id">
<option value="">Select a category</option>
<?php
$categories = $db->getRows("SELECT * FROM categories WHERE is_active = 1 ORDER BY sort_order");
foreach ($categories as $cat):
?>
<option value="<?php echo $cat['id']; ?>"><?php echo htmlspecialchars($cat['name']); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label for="rating" class="form-label">
<i class="fas fa-star me-2"></i>Rating
</label>
<div id="rating"></div>
<input type="hidden" id="rating_value" name="rating" value="0">
</div>
<div class="mb-3">
<label for="message" class="form-label">
<i class="fas fa-comment me-2"></i>Your Feedback
</label>
<textarea class="form-control" id="message" name="message" rows="5" 
placeholder="Please share your feedback, suggestions, or concerns" required></textarea>
</div>
</div>
<?php elseif ($form['type'] == 'nps'): ?>
<!-- NPS Survey -->
<div class="mb-4">
<h5 class="mb-3">How likely are you to recommend us?</h5>
<p class="text-muted small mb-4">Scale: 0 (Not at all likely) - 10 (Extremely likely)</p>
<div class="nps-scale mb-4">
<?php for ($i = 0; $i <= 10; $i++): ?>
<div class="nps-option">
<input type="radio" class="btn-check" name="nps_score" id="nps<?php echo $i; ?>" value="<?php echo $i; ?>" required>
<label class="btn btn-outline-primary" for="nps<?php echo $i; ?>"><?php echo $i; ?></label>
</div>
<?php endfor; ?>
</div>
<div class="mb-3">
<label for="message" class="form-label">
<i class="fas fa-comment me-2"></i>What's the primary reason for your score?
</label>
<textarea class="form-control" id="message" name="message" rows="4" required></textarea>
</div>
</div>
<?php elseif ($form['type'] == 'csat'): ?>
<!-- CSAT Survey -->
<div class="mb-4">
<h5 class="mb-3">How satisfied are you with our service?</h5>
<div class="csat-scale mb-4">
<div class="row text-center">
<div class="col">
<input type="radio" class="btn-check" name="csat_score" id="csat1" value="1" required>
<label class="btn btn-outline-danger" for="csat1">Very Unsatisfied</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="csat_score" id="csat2" value="2" required>
<label class="btn btn-outline-warning" for="csat2">Unsatisfied</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="csat_score" id="csat3" value="3" required>
<label class="btn btn-outline-info" for="csat3">Neutral</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="csat_score" id="csat4" value="4" required>
<label class="btn btn-outline-success" for="csat4">Satisfied</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="csat_score" id="csat5" value="5" required>
<label class="btn btn-outline-success" for="csat5">Very Satisfied</label>
</div>
</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">
<i class="fas fa-comment me-2"></i>Additional Comments
</label>
<textarea class="form-control" id="message" name="message" rows="4"></textarea>
</div>
</div>
<?php elseif ($form['type'] == 'ces'): ?>
<!-- CES Survey -->
<div class="mb-4">
<h5 class="mb-3">How easy was it to interact with us?</h5>
<div class="ces-scale mb-4">
<div class="row text-center">
<div class="col">
<input type="radio" class="btn-check" name="ces_score" id="ces1" value="1" required>
<label class="btn btn-outline-danger" for="ces1">Very Difficult</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="ces_score" id="ces2" value="2" required>
<label class="btn btn-outline-warning" for="ces2">Difficult</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="ces_score" id="ces3" value="3" required>
<label class="btn btn-outline-info" for="ces3">Neutral</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="ces_score" id="ces4" value="4" required>
<label class="btn btn-outline-success" for="ces4">Easy</label>
</div>
<div class="col">
<input type="radio" class="btn-check" name="ces_score" id="ces5" value="5" required>
<label class="btn btn-outline-success" for="ces5">Very Easy</label>
</div>
</div>
</div>
<div class="mb-3">
<label for="message" class="form-label">
<i class="fas fa-comment me-2"></i>What could we improve?
</label>
<textarea class="form-control" id="message" name="message" rows="4"></textarea>
</div>
</div>
<?php endif; ?>
<!-- Attachments -->
<div class="mb-4">
<h5 class="mb-3">Attachments</h5>
<div class="mb-3">
<label for="attachments" class="form-label">
<i class="fas fa-paperclip me-2"></i>Upload Files
</label>
<input type="file" class="form-control" id="attachments" name="attachments[]" multiple>
<small class="text-muted">
Allowed types: <?php echo implode(', ', ALLOWED_FILE_TYPES); ?>. Max size: <?php echo MAX_FILE_SIZE / 1024 / 1024; ?>MB
</small>
</div>
</div>
<!-- Tags -->
<div class="mb-4">
<label for="tags" class="form-label">
<i class="fas fa-tags me-2"></i>Tags
</label>
<input type="text" class="form-control" id="tags" name="tags" 
placeholder="Enter tags separated by commas">
<small class="text-muted">Example: bug, feature-request, urgent</small>
</div>
<button type="submit" class="btn btn-primary w-100 py-3">
<i class="fas fa-paper-plane me-2"></i>
Submit Feedback
</button>
</form>
</div>
<div class="card-footer bg-light text-center py-3">
<small class="text-muted">
Your feedback helps us improve our products and services.
<?php if (!$auth->isLoggedIn()): ?>
<br><a href="../login.php">Login</a> to track your feedback status.
<?php endif; ?>
</small>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rateYo/2.3.2/jquery.rateyo.min.js"></script>
<script>
// Star rating
$("#rating").rateYo({
rating: 0,
fullStar: true,
starWidth: "30px",
spacing: "5px",
onSet: function (rating) {
$("#rating_value").val(rating);
}
});
// Form validation
function validateForm() {
<?php if (!$auth->isLoggedIn()): ?>
const name = document.getElementById('name').value.trim();
const email = document.getElementById('email').value.trim();
if (name === '') {
alert('Please enter your name');
return false;
}
if (email === '') {
alert('Please enter your email');
return false;
}
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailPattern.test(email)) {
alert('Please enter a valid email address');
return false;
}
<?php endif; ?>
<?php if (!$form || $form['type'] == 'general'): ?>
const subject = document.getElementById('subject').value.trim();
const message = document.getElementById('message').value.trim();
if (subject === '') {
alert('Please enter a subject');
return false;
}
if (message === '') {
alert('Please enter your feedback');
return false;
}
<?php endif; ?>
return true;
}
</script>
<style>
.nps-scale {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 5px;
}
.nps-option {
flex: 1;
min-width: 40px;
}
.nps-option .btn {
width: 100%;
padding: 10px 0;
}
.csat-scale .btn,
.ces-scale .btn {
padding: 10px 15px;
white-space: nowrap;
}
@media (max-width: 768px) {
.nps-scale {
flex-wrap: wrap;
}
.nps-option {
flex: 0 0 calc(20% - 5px);
}
.csat-scale .row,
.ces-scale .row {
flex-direction: column;
gap: 10px;
}
}
</style>
</body>
</html>

Admin Dashboard

File: admin/dashboard.php

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
// Require admin role
$auth->requireRole('admin');
$userId = $_SESSION['user_id'];
// Initialize classes
$feedback = new Feedback();
$user = new User();
// Get statistics
$stats = $feedback->getDashboardStats();
// Get recent feedback
$recentFeedback = $feedback->getFeedbackList(['limit' => 10]);
// Get chart data
$chartData = $feedback->getChartData('week');
// Get user statistics
$totalUsers = $db->getValue("SELECT COUNT(*) FROM users");
$activeUsers = $db->getValue("SELECT COUNT(*) FROM users WHERE is_active = 1");
$newUsersToday = $db->getValue("SELECT COUNT(*) FROM users WHERE DATE(created_at) = CURDATE()");
// Get unread notifications
$unreadNotifications = getUnreadNotificationsCount($userId);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - <?php echo APP_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- DataTables CSS -->
<link href="https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap5.min.css" rel="stylesheet">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Custom CSS -->
<link rel="stylesheet" href="../assets/css/dashboard.css">
</head>
<body>
<div class="d-flex">
<!-- Sidebar -->
<div class="sidebar bg-dark text-white">
<div class="sidebar-header p-4">
<a href="dashboard.php" class="text-white text-decoration-none">
<i class="fas fa-chart-pie fa-2x mb-3"></i>
<h5 class="mb-0"><?php echo APP_NAME; ?></h5>
<small>Admin Panel</small>
</a>
</div>
<div class="user-info p-4 border-top border-secondary">
<div class="d-flex align-items-center">
<div class="avatar bg-primary rounded-circle d-flex align-items-center justify-content-center me-3" 
style="width: 50px; height: 50px;">
<?php echo strtoupper(substr($_SESSION['user_name'], 0, 1)); ?>
</div>
<div>
<h6 class="mb-0"><?php echo htmlspecialchars($_SESSION['user_name']); ?></h6>
<small class="text-secondary">Administrator</small>
</div>
</div>
</div>
<nav class="nav flex-column p-3">
<a href="dashboard.php" class="nav-link text-white active">
<i class="fas fa-tachometer-alt me-3"></i>Dashboard
</a>
<a href="feedback.php" class="nav-link text-white">
<i class="fas fa-comment me-3"></i>Feedback
<?php if ($stats['new'] > 0): ?>
<span class="badge bg-danger ms-auto"><?php echo $stats['new']; ?></span>
<?php endif; ?>
</a>
<a href="users.php" class="nav-link text-white">
<i class="fas fa-users me-3"></i>Users
</a>
<a href="categories.php" class="nav-link text-white">
<i class="fas fa-tags me-3"></i>Categories
</a>
<a href="reports.php" class="nav-link text-white">
<i class="fas fa-chart-bar me-3"></i>Reports
</a>
<a href="settings.php" class="nav-link text-white">
<i class="fas fa-cog me-3"></i>Settings
</a>
<a href="../logout.php" class="nav-link text-white mt-4">
<i class="fas fa-sign-out-alt me-3"></i>Logout
</a>
</nav>
</div>
<!-- Main Content -->
<div class="main-content flex-grow-1 bg-light">
<header class="bg-white shadow-sm p-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-tachometer-alt me-2 text-primary"></i>
Dashboard
</h5>
<div class="d-flex align-items-center">
<!-- Notifications -->
<div class="dropdown me-3">
<button class="btn btn-light position-relative" type="button" data-bs-toggle="dropdown">
<i class="fas fa-bell"></i>
<?php if ($unreadNotifications > 0): ?>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?php echo $unreadNotifications; ?>
</span>
<?php endif; ?>
</button>
</div>
<!-- Date -->
<span class="text-muted">
<i class="fas fa-calendar me-1"></i>
<?php echo date('F j, Y'); ?>
</span>
</div>
</div>
</header>
<div class="p-4">
<!-- Statistics Cards -->
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">Total Feedback</h6>
<h3 class="mb-0"><?php echo number_format($stats['total'] ?? 0); ?></h3>
</div>
<div class="icon-circle bg-primary bg-opacity-10 text-primary">
<i class="fas fa-comment"></i>
</div>
</div>
<small class="text-muted">All time</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">New This Week</h6>
<h3 class="mb-0"><?php echo number_format($stats['week'] ?? 0); ?></h3>
</div>
<div class="icon-circle bg-success bg-opacity-10 text-success">
<i class="fas fa-chart-line"></i>
</div>
</div>
<small class="text-muted">+<?php echo number_format($stats['week'] - $stats['last_week'] ?? 0); ?> from last week</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">Avg. Rating</h6>
<h3 class="mb-0"><?php echo $stats['avg_rating'] ?? 0; ?></h3>
</div>
<div class="icon-circle bg-warning bg-opacity-10 text-warning">
<i class="fas fa-star"></i>
</div>
</div>
<small class="text-muted">out of 5</small>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">Avg. Response Time</h6>
<h3 class="mb-0"><?php echo $stats['avg_response_time'] ?? 0; ?>h</h3>
</div>
<div class="icon-circle bg-info bg-opacity-10 text-info">
<i class="fas fa-clock"></i>
</div>
</div>
<small class="text-muted">to resolution</small>
</div>
</div>
</div>
</div>
<!-- Charts Row -->
<div class="row mb-4">
<div class="col-md-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">Feedback Trends</h6>
</div>
<div class="card-body">
<canvas id="feedbackChart" height="300"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white">
<h6 class="mb-0">Status Distribution</h6>
</div>
<div class="card-body">
<canvas id="statusChart" height="250"></canvas>
</div>
</div>
</div>
</div>
<!-- Recent Feedback Table -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0">Recent Feedback</h6>
<a href="feedback.php" class="btn btn-sm btn-primary">View All</a>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Customer</th>
<th>Subject</th>
<th>Rating</th>
<th>Status</th>
<th>Priority</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentFeedback as $item): ?>
<tr>
<td>
<small><?php echo $item['feedback_id']; ?></small>
</td>
<td>
<?php echo htmlspecialchars($item['customer_name'] ?: $item['customer_email'] ?: 'Anonymous'); ?>
</td>
<td>
<?php echo htmlspecialchars(truncateText($item['subject'] ?: 'No subject', 30)); ?>
</td>
<td>
<?php if ($item['rating']): ?>
<?php for ($i = 1; $i <= 5; $i++): ?>
<i class="fas fa-star <?php echo $i <= $item['rating'] ? 'text-warning' : 'text-muted'; ?>"></i>
<?php endfor; ?>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?php echo getStatusBadge($item['status']); ?></td>
<td><?php echo getPriorityBadge($item['priority']); ?></td>
<td><?php echo formatDate($item['created_at'], 'M d, Y'); ?></td>
<td>
<a href="feedback_details.php?id=<?php echo $item['id']; ?>" class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap5.min.js"></script>
<script>
// Feedback Chart
const ctx = document.getElementById('feedbackChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: <?php echo json_encode($chartData['labels']); ?>,
datasets: [{
label: 'Feedback Count',
data: <?php echo json_encode($chartData['values']); ?>,
borderColor: '#4e73df',
backgroundColor: 'rgba(78, 115, 223, 0.05)',
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
// Status Distribution Chart
const statusCtx = document.getElementById('statusChart').getContext('2d');
<?php
$statusStats = $db->getRows(
"SELECT status, COUNT(*) as count FROM feedback GROUP BY status"
);
$statusLabels = [];
$statusData = [];
$statusColors = [
'new' => '#4e73df',
'in_progress' => '#f6c23e',
'resolved' => '#1cc88a',
'closed' => '#858796',
'spam' => '#e74a3b'
];
foreach ($statusStats as $stat) {
$statusLabels[] = ucfirst(str_replace('_', ' ', $stat['status']));
$statusData[] = $stat['count'];
}
?>
new Chart(statusCtx, {
type: 'doughnut',
data: {
labels: <?php echo json_encode($statusLabels); ?>,
datasets: [{
data: <?php echo json_encode($statusData); ?>,
backgroundColor: <?php echo json_encode(array_values($statusColors)); ?>,
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
},
cutout: '70%'
}
});
</script>
<style>
.sidebar {
width: 280px;
min-height: 100vh;
}
.main-content {
margin-left: 280px;
}
.nav-link {
color: rgba(255,255,255,0.8) !important;
padding: 12px 20px;
border-radius: 5px;
margin: 2px 10px;
transition: all 0.3s;
}
.nav-link:hover,
.nav-link.active {
background: rgba(255,255,255,0.1) !important;
color: white !important;
}
.nav-link i {
width: 20px;
}
.icon-circle {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
position: fixed;
z-index: 1000;
}
.main-content {
margin-left: 0;
}
}
</style>
</body>
</html>

Environment Configuration

File: .env

# Database Configuration
DB_HOST=localhost
DB_NAME=feedback_system
DB_USER=root
DB_PASS=
# Application Configuration
APP_NAME=Customer Feedback System
APP_URL=http://localhost/feedback-system
APP_VERSION=1.0.0
DEBUG_MODE=true
# Security
SESSION_TIMEOUT=3600
BCRYPT_ROUNDS=12
# Upload Configuration
MAX_FILE_SIZE=5242880  # 5MB in bytes
# Pagination
ITEMS_PER_PAGE=20
# Date/Time
TIMEZONE=America/New_York
DATE_FORMAT=Y-m-d
TIME_FORMAT=H:i
# Email Configuration
[email protected]
COMPANY_NAME=Feedback System
ENABLE_NOTIFICATIONS=true
# Feedback Settings
ENABLE_SENTIMENT=true
AUTO_ASSIGN=false
AUTO_RESPONSE=true
AUTO_RESPONSE_MESSAGE="Thank you for your feedback. We have received your submission and will review it shortly."

File: .gitignore

# Environment variables
.env
# Dependencies
/vendor/
node_modules/
# IDE files
.vscode/
.idea/
*.sublime-*
# OS files
.DS_Store
Thumbs.db
# Logs
/logs/
*.log
# Uploads
/uploads/
!/uploads/.gitkeep
!/uploads/attachments/.gitkeep
# Cache
/cache/
!/cache/.gitkeep
# Composer
composer.lock
# Temp files
*.tmp
*.temp

File: composer.json

{
"name": "feedback-system/application",
"description": "Customer Feedback System",
"type": "project",
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "^6.8",
"tecnickcom/tcpdf": "^6.6",
"phpoffice/phpspreadsheet": "^1.29"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"FeedbackSystem\\": "src/"
}
},
"scripts": {
"test": "phpunit tests",
"post-install-cmd": [
"chmod -R 755 uploads/",
"chmod -R 755 logs/",
"chmod -R 755 cache/"
]
}
}

How to Use the Project (Step-by-Step Guide)

Prerequisites

  1. Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
  2. Database: MySQL 5.7+ or MariaDB
  3. Composer: For dependency management
  4. Browser: Modern web browser (Chrome, Firefox, Edge, etc.)

Installation Steps

Step 1: Set Up Local Server

  1. Download and install XAMPP from https://www.apachefriends.org/
  2. Launch XAMPP Control Panel
  3. Start Apache and MySQL services

Step 2: Install Composer Dependencies

  1. Download and install Composer from https://getcomposer.org/
  2. Navigate to your project directory in terminal
  3. Run: composer install

Step 3: Create Project Folder

  1. Navigate to C:\xampp\htdocs\ (Windows) or /Applications/XAMPP/htdocs/ (Mac)
  2. Create a new folder named feedback-system
  3. Create all the folders and files as shown in the Project File Structure

Step 4: Set Up Database

  1. Open browser and go to http://localhost/phpmyadmin
  2. Click on "New" to create a new database
  3. Name the database feedback_system and select utf8mb4_general_ci
  4. Click on "Import" tab
  5. Click "Choose File" and select the database.sql file from the sql folder
  6. Click "Go" to import the database structure and sample data

Step 5: Configure Environment

  1. Rename .env.example to .env in the project root
  2. Update database credentials if different from default:
   DB_HOST=localhost
DB_NAME=feedback_system
DB_USER=root
DB_PASS=
  1. Update application URL:
   APP_URL=http://localhost/feedback-system
  1. Configure email settings if using notifications
  2. Adjust other settings as needed

Step 6: Set Folder Permissions

Create the following folders and ensure they are writable:

  • uploads/attachments/
  • logs/
  • cache/

On Windows, right-click folders → Properties → Security → give Write permission to Users
On Mac/Linux, run: chmod -R 755 uploads/ logs/ cache/

Step 7: Create Admin Password Hash

  1. Go to http://localhost/feedback-system/register.php
  2. Register a test user (e.g., email: [email protected], password: Admin@123)
  3. Open phpMyAdmin, go to the users table
  4. Find the user and change role to 'admin'

Step 8: Test the Installation

  1. Open browser and go to http://localhost/feedback-system/
  2. You should see the landing page (or login redirect)
  3. Test different user types: Admin Login:
  • Email: [email protected]
  • Password: Admin@123 Customer Feedback:
  • Go to http://localhost/feedback-system/customer/submit.php
  • Submit test feedback

System Walkthrough

For Customers:

  1. Submit Feedback - Fill out feedback form with rating and comments
  2. Track Status - Login to view feedback history and status
  3. Receive Responses - Get email notifications when responded to
  4. View Resolution - See when feedback is marked as resolved

For Managers/Analysts:

  1. Dashboard - View key metrics and recent feedback
  2. Manage Feedback - View, filter, and search all feedback
  3. Respond to Feedback - Add public/private responses
  4. Assign Feedback - Assign items to team members
  5. Update Status - Change status as feedback is processed
  6. Generate Reports - Create and export reports

For Admins:

  1. User Management - Create, edit, and manage users
  2. Category Management - Create and manage feedback categories
  3. Tag Management - Create and manage tags
  4. System Settings - Configure application settings
  5. View All Data - Access all feedback and analytics

Key Features Explained

Feedback Submission

  1. Customers fill out form with rating and comments
  2. Optional attachments can be uploaded
  3. Sentiment is automatically analyzed
  4. Priority is calculated based on content and rating
  5. Auto-response email sent if enabled

Feedback Management

  1. View all feedback in sortable/filterable table
  2. Change status (New, In Progress, Resolved, Closed)
  3. Add responses (public or private)
  4. Assign to team members
  5. Add tags for better organization
  6. View feedback history and activity log

Analytics & Reporting

  1. Real-time dashboard with key metrics
  2. Trend charts for feedback volume
  3. Status distribution visualization
  4. Average ratings and NPS scores
  5. Response time tracking
  6. Export reports to PDF/Excel

Notification System

  1. Email notifications for new feedback
  2. Assignment notifications to team members
  3. Response notifications to customers
  4. Resolution notifications
  5. Daily/weekly digest emails

Troubleshooting

Common Issues and Solutions

  1. Database Connection Error
  • Check if MySQL is running
  • Verify database credentials in .env
  • Ensure database feedback_system exists
  1. 404 Page Not Found
  • Check file paths and folder structure
  • Verify APP_URL in .env
  • Ensure .htaccess is properly configured (if using Apache)
  1. File Upload Issues
  • Check folder permissions (uploads/ must be writable)
  • Verify MAX_FILE_SIZE in .env
  • Check allowed file types
  1. Email Not Sending
  • Configure SMTP settings in PHPMailer
  • Check spam folder
  • Verify PHP mail() function is enabled
  1. Sentiment Analysis Not Working
  • Ensure ENABLE_SENTIMENT=true in .env
  • Check that message text is being passed correctly

Security Best Practices

  1. Change default admin password immediately after installation
  2. Use HTTPS in production with SSL certificate
  3. Regular backups of database and uploads
  4. Input validation on both client and server side
  5. SQL injection prevention using prepared statements
  6. XSS prevention using htmlspecialchars()
  7. CSRF tokens for all forms
  8. Password hashing using bcrypt
  9. Rate limiting for login attempts
  10. File upload validation and malware scanning

Performance Optimizations

  1. Database indexing on frequently queried columns
  2. Query caching for repeated requests
  3. Image optimization for uploaded files
  4. Lazy loading for images and content
  5. Pagination for large datasets
  6. Minified CSS and JavaScript for production
  7. CDN for static assets
  8. Browser caching headers
  9. Database query optimization
  10. Archive old feedback to separate tables

Deployment to Production

  1. Update .env with production settings
  2. Set DEBUG_MODE=false
  3. Configure proper error logging
  4. Set up SSL certificate
  5. Configure cron jobs for reports and digests:
   # Generate daily reports at 2 AM
0 2 * * * php /path/to/project/cron/generate_reports.php
# Send weekly digests on Monday at 8 AM
0 8 * * 1 php /path/to/project/cron/send_digest.php
  1. Set up database backups
  2. Configure CDN for static assets
  3. Enable caching headers
  4. Set up monitoring and alerts

Conclusion

The Customer Feedback System is a comprehensive, feature-rich application for collecting, managing, and analyzing customer feedback. With its modular architecture, multiple feedback types, and powerful analytics, it provides businesses with the tools needed to understand customer sentiment and improve their products and services.

This application demonstrates:

  • Multiple feedback collection methods (General, NPS, CSAT, CES)
  • Advanced analytics with sentiment analysis and trend detection
  • Role-based access control (Admin, Manager, Analyst, Customer)
  • Feedback management workflow with status tracking and assignment
  • Response system for engaging with customers
  • Tagging and categorization for better organization
  • Reporting and export capabilities
  • Email notifications for timely communication
  • File attachments for supporting documentation
  • Real-time dashboard with key metrics

The system is built to be extensible, allowing easy addition of new features such as:

  • Integration with CRM systems
  • Custom survey builders
  • Machine learning for advanced sentiment analysis
  • Multi-language support
  • API for third-party integrations
  • Mobile apps for on-the-go feedback collection

Whether you're running a small business or a large enterprise, this customer feedback system provides a solid foundation for understanding your customers and making data-driven decisions to improve satisfaction and loyalty.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper