|
Server : LiteSpeed System : Linux terra.hostitbro.com 5.14.0-611.54.3.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Thu May 7 16:31:24 EDT 2026 x86_64 User : outerorb ( 1091) PHP Version : 8.1.34 Disable Function : mail Directory : /home2/outerorb/emp.outerorbittech.in/admin/ | |
|
Path: /home2/outerorb/emp.outerorbittech.in/admin/appraisals.php
Size: 21.66 KB
Permissions: 0666
<?php
require __DIR__ . '/../includes/helpers.php';
require_admin();
ensure_appraisal_tables();
ensure_employees_table();
$pdo = db();
$flash = flash();
$admin = current_admin();
$errors = [];
$view = $_GET['view'] ?? 'cycles';
$cycleId = isset($_GET['cycle']) ? (int)$_GET['cycle'] : 0;
// --- POST ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!verify_csrf($_POST['csrf_token'] ?? '')) {
redirect_with_message('appraisals.php', 'Session expired.', 'error');
}
$action = $_POST['action'] ?? '';
// Create / update cycle
if ($action === 'save_cycle') {
$id = (int)($_POST['id'] ?? 0);
$title = sanitize_text($_POST['title'] ?? '');
$type = sanitize_text($_POST['type'] ?? 'annual');
$year = (int)($_POST['year'] ?? date('Y'));
$from = sanitize_text($_POST['from_date'] ?? '');
$to = sanitize_text($_POST['to_date'] ?? '');
if (!$title) $errors[] = 'Title is required.';
if (!$from || !$to) $errors[] = 'Review period dates are required.';
if (empty($errors)) {
if ($id) {
$pdo->prepare('UPDATE appraisal_cycles SET title=?,type=?,year=?,from_date=?,to_date=?,updated_at=NOW() WHERE id=?')
->execute([$title, $type, $year, $from, $to, $id]);
} else {
$pdo->prepare('INSERT INTO appraisal_cycles (title,type,year,from_date,to_date,created_by) VALUES (?,?,?,?,?,?)')
->execute([$title, $type, $year, $from, $to, $admin['id'] ?? null]);
}
redirect_with_message('appraisals.php', $id ? 'Cycle updated.' : 'Cycle created.');
}
}
// Open / close cycle
if ($action === 'set_status') {
$id = (int)($_POST['cycle_id'] ?? 0);
$status = in_array($_POST['status'] ?? '', ['draft','open','closed']) ? $_POST['status'] : 'draft';
if ($id) {
$pdo->prepare('UPDATE appraisal_cycles SET status=?,updated_at=NOW() WHERE id=?')->execute([$status, $id]);
}
redirect_with_message('appraisals.php?view=employees&cycle='.$id, 'Status updated.');
}
// Save/update appraisal for an employee
if ($action === 'save_appraisal') {
$cid = (int)($_POST['cycle_id'] ?? 0);
$eid = (int)($_POST['employee_id'] ?? 0);
$aid = (int)($_POST['appraisal_id'] ?? 0);
$selfR = min(5, max(1, (int)($_POST['self_rating'] ?? 0)));
$mgrR = min(5, max(1, (int)($_POST['manager_rating'] ?? 0)));
$selfC = sanitize_text($_POST['self_comments'] ?? '');
$mgrC = sanitize_text($_POST['manager_comments'] ?? '');
if ($cid && $eid) {
if ($aid) {
$pdo->prepare(
'UPDATE appraisals SET self_rating=?,self_comments=?,manager_rating=?,manager_comments=?,updated_at=NOW() WHERE id=?'
)->execute([$selfR, $selfC ?: null, $mgrR, $mgrC ?: null, $aid]);
} else {
$pdo->prepare(
'INSERT INTO appraisals (cycle_id,employee_id,self_rating,self_comments,manager_rating,manager_comments) VALUES (?,?,?,?,?,?)'
)->execute([$cid, $eid, $selfR, $selfC ?: null, $mgrR, $mgrC ?: null]);
}
}
redirect_with_message('appraisals.php?view=employees&cycle='.$cid, 'Appraisal saved.');
}
// Delete cycle
if ($action === 'delete_cycle') {
$id = (int)($_POST['cycle_id'] ?? 0);
if ($id) {
$pdo->prepare('DELETE FROM appraisals WHERE cycle_id=?')->execute([$id]);
$pdo->prepare('DELETE FROM appraisal_cycles WHERE id=? AND status=\'draft\'')->execute([$id]);
}
redirect_with_message('appraisals.php', 'Cycle deleted.');
}
}
// --- Load data ---
$cycles = $pdo->query('SELECT ac.*, (SELECT COUNT(*) FROM appraisals WHERE cycle_id=ac.id) AS appraisal_count FROM appraisal_cycles ac ORDER BY ac.year DESC, ac.id DESC')->fetchAll();
$cycle = null;
$empRecords = [];
if ($cycleId) {
$cycle = $pdo->prepare('SELECT * FROM appraisal_cycles WHERE id=? LIMIT 1');
$cycle->execute([$cycleId]);
$cycle = $cycle->fetch() ?: null;
if ($cycle) {
$stmt = $pdo->prepare(
'SELECT e.id AS emp_id, e.first_name, e.last_name, e.department, e.designation,
a.id AS appraisal_id, a.self_rating, a.self_comments, a.manager_rating, a.manager_comments
FROM employees e
LEFT JOIN appraisals a ON a.employee_id = e.id AND a.cycle_id = ?
WHERE e.deleted_at IS NULL AND e.employee_status = \'active\'
ORDER BY e.department, e.first_name'
);
$stmt->execute([$cycleId]);
$empRecords = $stmt->fetchAll();
}
}
$editCycle = null;
if (isset($_GET['edit'])) {
$ec = $pdo->prepare('SELECT * FROM appraisal_cycles WHERE id=? LIMIT 1');
$ec->execute([(int)$_GET['edit']]);
$editCycle = $ec->fetch() ?: null;
}
function e(string $v): string { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); }
$ratingLabel = fn($r) => [
'',
'<i class="fas fa-star" style="color:gold;"></i>',
'<i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i>',
'<i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i>',
'<i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i>',
'<i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i><i class="fas fa-star" style="color:gold;"></i>'
][max(0,min(5,(int)$r))] ?? '';
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Appraisals — HRMS</title>
<link rel="stylesheet" href="../assets/css/style.css?v=<?php echo filemtime(__DIR__.'/../assets/css/style.css'); ?>" />
<link rel="stylesheet" href="../assets/css/polish.css">
<style>
.cycle-card { border:1px solid #e5e7eb; border-radius:10px; padding:16px 20px; margin-bottom:12px; display:flex; justify-content:space-between; align-items:center; }
.status-badge { display:inline-block; padding:2px 10px; border-radius:999px; font-size:.72rem; font-weight:700; }
.stars { letter-spacing:-1px; font-size:1rem; }
.modal-overlay { display:none; position:fixed; inset:0; background:rgba(0,0,0,.4); z-index:999; align-items:center; justify-content:center; }
.modal-overlay.open { display:flex; }
</style>
</head>
<body class="dashboard-layout">
<div class="app-wrapper">
<?php require '_sidebar.php'; ?>
<div class="main-content">
<div class="top-bar">
<div class="top-bar-left">
<button class="sidebar-toggle" id="sidebarToggle" aria-label="Toggle sidebar">โฐ</button>
<div>
<h1 class="page-title">Appraisals</h1>
<div class="breadcrumb-nav"><span>Manage performance review cycles.</span></div>
</div>
</div>
<div class="top-bar-right">
<a href="dashboard.php" class="badge">Dashboard</a>
<a href="logout.php" class="badge">Logout</a>
</div>
</div>
<div class="dashboard-container">
<?php if ($flash): ?>
<div class="alert <?php echo $flash['type']==='error' ? 'alert-error' : 'alert-success'; ?>">
<?php echo e($flash['message']); ?>
</div>
<?php endif; ?>
<?php foreach ($errors as $err): ?>
<div class="alert alert-error"><?php echo e($err); ?></div>
<?php endforeach; ?>
<!-- ===== CYCLES VIEW ===== -->
<?php if ($view === 'cycles'): ?>
<div class="card" style="margin-bottom:16px;">
<h4 style="margin:0 0 14px; font-size:.85rem; text-transform:uppercase; letter-spacing:.04em; color:#6b7280;">
<?php echo $editCycle ? 'Edit Cycle' : 'New Appraisal Cycle'; ?>
</h4>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="save_cycle" />
<?php if ($editCycle): ?>
<input type="hidden" name="id" value="<?php echo (int)$editCycle['id']; ?>" />
<?php endif; ?>
<div style="display:grid; grid-template-columns:2fr 1fr 1fr 1fr 1fr; gap:12px; align-items:end;">
<div class="form-group" style="margin:0;">
<label>Cycle Title</label>
<input type="text" name="title" class="form-control" required
value="<?php echo e($editCycle ? $editCycle['title'] : 'Annual Review '.date('Y')); ?>" />
</div>
<div class="form-group" style="margin:0;">
<label>Type</label>
<select name="type" class="form-control">
<?php foreach (['annual','h1','h2','quarterly'] as $t): ?>
<option value="<?php echo $t; ?>" <?php echo ($editCycle && $editCycle['type']===$t) ? 'selected' : ''; ?>><?php echo ucfirst($t); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="margin:0;">
<label>Year</label>
<input type="number" name="year" class="form-control" min="2020" max="2099"
value="<?php echo $editCycle ? (int)$editCycle['year'] : date('Y'); ?>" />
</div>
<div class="form-group" style="margin:0;">
<label>From Date</label>
<input type="date" name="from_date" class="form-control" required
value="<?php echo e($editCycle ? $editCycle['from_date'] : ''); ?>" />
</div>
<div class="form-group" style="margin:0;">
<label>To Date</label>
<input type="date" name="to_date" class="form-control" required
value="<?php echo e($editCycle ? $editCycle['to_date'] : ''); ?>" />
</div>
</div>
<button type="submit" class="btn btn-primary" style="margin-top:12px;">
<?php echo $editCycle ? 'Update Cycle' : 'Create Cycle'; ?>
</button>
<?php if ($editCycle): ?>
<a href="appraisals.php" style="margin-left:10px; font-size:.85rem; color:#6b7280;">Cancel</a>
<?php endif; ?>
</form>
</div>
<?php if (empty($cycles)): ?>
<div class="card" style="text-align:center; padding:40px; color:#6b7280;">No appraisal cycles created yet.</div>
<?php else: ?>
<?php foreach ($cycles as $cyc):
$statusColor = ['draft'=>'#fef3c7;color:#92400e','open'=>'#d1fae5;color:#065f46','closed'=>'#f3f4f6;color:#374151'][$cyc['status']] ?? '#f3f4f6;color:#374151';
?>
<div class="cycle-card">
<div>
<div style="font-weight:700; font-size:.95rem;"><?php echo e($cyc['title']); ?></div>
<div style="font-size:.78rem; color:#6b7280; margin-top:3px;">
<?php echo ucfirst($cyc['type']); ?> • <?php echo $cyc['year']; ?> •
<?php echo date('d M', strtotime($cyc['from_date'])); ?> รขโฌโ <?php echo date('d M Y', strtotime($cyc['to_date'])); ?>
• <?php echo (int)$cyc['appraisal_count']; ?> appraisals
</div>
</div>
<div style="display:flex; align-items:center; gap:10px;">
<span class="status-badge" style="background:<?php echo $statusColor; ?>;">
<?php echo ucfirst($cyc['status']); ?>
</span>
<a class="badge" href="appraisals.php?view=employees&cycle=<?php echo (int)$cyc['id']; ?>">
<?php echo $cyc['status']==='closed' ? 'Results' : 'Manage'; ?>
</a>
<a href="appraisals.php?edit=<?php echo (int)$cyc['id']; ?>" style="font-size:.82rem; color:#6b7280;">Edit</a>
<?php if ($cyc['status'] === 'draft'): ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('Delete this cycle?');">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="delete_cycle" />
<input type="hidden" name="cycle_id" value="<?php echo (int)$cyc['id']; ?>" />
<button type="submit" style="background:none; border:none; color:#ef4444; cursor:pointer; font-size:.82rem;">Delete</button>
</form>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
<!-- ===== EMPLOYEES VIEW ===== -->
<?php elseif ($view === 'employees' && $cycle): ?>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">
<div>
<h2 style="margin:0; font-size:1.05rem;"><?php echo e($cycle['title']); ?></h2>
<div style="font-size:.8rem; color:#6b7280;">
<?php echo date('d M Y', strtotime($cycle['from_date'])); ?> รขโฌโ <?php echo date('d M Y', strtotime($cycle['to_date'])); ?>
</div>
</div>
<div style="display:flex; gap:8px;">
<?php if ($cycle['status'] === 'draft'): ?>
<form method="POST" style="display:inline;">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="set_status" />
<input type="hidden" name="cycle_id" value="<?php echo (int)$cycle['id']; ?>" />
<input type="hidden" name="status" value="open" />
<button type="submit" style="background:#2563eb; color:#fff; border:none; padding:6px 14px; border-radius:6px; cursor:pointer; font-size:.82rem;">Open Cycle</button>
</form>
<?php elseif ($cycle['status'] === 'open'): ?>
<form method="POST" style="display:inline;" onsubmit="return confirm('Close cycle? Employees will no longer be able to submit self-assessments.');">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="set_status" />
<input type="hidden" name="cycle_id" value="<?php echo (int)$cycle['id']; ?>" />
<input type="hidden" name="status" value="closed" />
<button type="submit" style="background:#6b7280; color:#fff; border:none; padding:6px 14px; border-radius:6px; cursor:pointer; font-size:.82rem;">Close Cycle</button>
</form>
<?php endif; ?>
</div>
</div>
<?php if (empty($empRecords)): ?>
<div class="card" style="text-align:center; padding:40px; color:#6b7280;">No active employees found.</div>
<?php else: ?>
<div class="card" style="overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; font-size:.82rem;">
<thead>
<tr style="background:#f9fafb;">
<th style="padding:9px 12px; border-bottom:1px solid #e5e7eb; text-align:left;">Employee</th>
<th style="padding:9px 12px; border-bottom:1px solid #e5e7eb; text-align:left;">Dept</th>
<th style="padding:9px 12px; border-bottom:1px solid #e5e7eb; text-align:center;">Self Rating</th>
<th style="padding:9px 12px; border-bottom:1px solid #e5e7eb; text-align:center;">Manager Rating</th>
<th style="padding:9px 12px; border-bottom:1px solid #e5e7eb; text-align:center;">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($empRecords as $er): ?>
<tr>
<td style="padding:8px 12px; border-bottom:1px solid #f3f4f6; font-weight:600;"><?php echo e($er['first_name'].' '.$er['last_name']); ?></td>
<td style="padding:8px 12px; border-bottom:1px solid #f3f4f6; color:#6b7280;"><?php echo e($er['department']); ?></td>
<td style="padding:8px 12px; border-bottom:1px solid #f3f4f6; text-align:center;">
<?php echo $er['self_rating'] ? '<span class="stars">'.$ratingLabel($er['self_rating']).'</span> ('.$er['self_rating'].')' : '<span style="color:#d1d5db;">โ</span>'; ?>
</td>
<td style="padding:8px 12px; border-bottom:1px solid #f3f4f6; text-align:center;">
<?php echo $er['manager_rating'] ? '<span class="stars">'.$ratingLabel($er['manager_rating']).'</span> ('.$er['manager_rating'].')' : '<span style="color:#d1d5db;">โ</span>'; ?>
</td>
<td style="padding:8px 12px; border-bottom:1px solid #f3f4f6; text-align:center;">
<?php if ($cycle['status'] !== 'closed'): ?>
<button type="button" onclick="openAppraisalModal(<?php echo htmlspecialchars(json_encode($er), ENT_QUOTES); ?>, <?php echo (int)$cycle['id']; ?>)"
style="background:none; border:none; color:#2563eb; cursor:pointer; font-size:.82rem;">
<?php echo $er['appraisal_id'] ? 'Edit' : 'Rate'; ?>
</button>
<?php else: ?>
<span style="color:#9ca3af; font-size:.78rem;">Closed</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<!-- Appraisal modal -->
<div id="appraisal-modal" class="modal-overlay">
<div style="background:#fff; border-radius:12px; padding:28px; width:440px; max-width:95vw;">
<h3 id="modal-emp-name" style="margin:0 0 18px; font-size:1rem;"></h3>
<form method="POST" action="appraisals.php?view=employees&cycle=<?php echo $cycleId; ?>">
<input type="hidden" name="csrf_token" value="<?php echo e(csrf_token()); ?>" />
<input type="hidden" name="action" value="save_appraisal" />
<input type="hidden" name="cycle_id" id="modal-cycle-id" />
<input type="hidden" name="employee_id" id="modal-emp-id" />
<input type="hidden" name="appraisal_id" id="modal-appraisal-id" />
<div style="display:grid; grid-template-columns:1fr 1fr; gap:12px;">
<div class="form-group">
<label>Self Rating (1รขโฌโ5)</label>
<select name="self_rating" id="modal-self-rating" class="form-control">
<?php for ($i=1;$i<=5;$i++): ?><option value="<?php echo $i; ?>"><?php echo $i; ?></option><?php endfor; ?>
</select>
</div>
<div class="form-group">
<label>Manager Rating (1รขโฌโ5)</label>
<select name="manager_rating" id="modal-mgr-rating" class="form-control">
<?php for ($i=1;$i<=5;$i++): ?><option value="<?php echo $i; ?>"><?php echo $i; ?></option><?php endfor; ?>
</select>
</div>
</div>
<div class="form-group">
<label>Self Comments</label>
<textarea name="self_comments" id="modal-self-comments" class="form-control" rows="2"></textarea>
</div>
<div class="form-group">
<label>Manager Comments</label>
<textarea name="manager_comments" id="modal-mgr-comments" class="form-control" rows="2"></textarea>
</div>
<div style="display:flex; gap:10px;">
<button type="submit" class="btn btn-primary" style="flex:1;">Save</button>
<button type="button" onclick="document.getElementById('appraisal-modal').classList.remove('open')"
style="flex:1; background:#f3f4f6; border:1px solid #e5e7eb; border-radius:6px; cursor:pointer;">Cancel</button>
</div>
</form>
</div>
</div>
<script>
function openAppraisalModal(emp, cycleId) {
document.getElementById('modal-emp-name').textContent = emp.first_name + ' ' + emp.last_name;
document.getElementById('modal-cycle-id').value = cycleId;
document.getElementById('modal-emp-id').value = emp.emp_id;
document.getElementById('modal-appraisal-id').value = emp.appraisal_id || '';
document.getElementById('modal-self-rating').value = emp.self_rating || 3;
document.getElementById('modal-mgr-rating').value = emp.manager_rating || 3;
document.getElementById('modal-self-comments').value = emp.self_comments || '';
document.getElementById('modal-mgr-comments').value = emp.manager_comments || '';
document.getElementById('appraisal-modal').classList.add('open');
}
document.getElementById('appraisal-modal').addEventListener('click', function(e) {
if (e.target === this) this.classList.remove('open');
});
</script>
</div>
</div>
</div>
</body>
</html>