major change

This commit is contained in:
root
2026-03-27 00:03:48 +07:00
parent 9b1d5753cb
commit f89fcbee1c
9 changed files with 2143 additions and 463 deletions

View File

@@ -57,45 +57,62 @@ $my_mood = $current_user['mood'] ?? null;
// ── Team moods (all users + their mood) ──────────────────────────────────────
$team_moods = query("SELECT id, username, mood, mood_updated_at FROM users WHERE mood IS NOT NULL AND id != ? ORDER BY mood_updated_at DESC", [$user_id])->fetchAll();
// ── Activity log (user + their groups, last 20) ───────────────────────────────
// ── Activity log ──────────────────────────────────────────────────────────────
// Admin: sees ALL activity across all users & groups
// Standard user: sees own activity + group members activity
$is_admin_user = !empty($_SESSION['is_admin']);
$recent_logs = [];
try {
// Collect relevant user IDs: current user + members of same groups
$peer_ids = [$user_id];
if ($my_group_ids) {
$gin = implode(',', array_fill(0, count($my_group_ids), '?'));
$peers = $pdo->prepare("SELECT DISTINCT user_id FROM user_groups WHERE group_id IN ($gin)");
$peers->execute($my_group_ids);
$peer_ids = array_unique(array_merge($peer_ids, array_column($peers->fetchAll(), 'user_id')));
if ($is_admin_user) {
// Admin: fetch all activity
$stmt = $pdo->prepare("SELECT al.*, u.username FROM activity_log al LEFT JOIN users u ON al.user_id=u.id ORDER BY al.created_at DESC LIMIT 40");
$stmt->execute();
} else {
// Standard: only own + group peers
$peer_ids = [$user_id];
if ($my_group_ids) {
$gin = implode(',', array_fill(0, count($my_group_ids), '?'));
$peers = $pdo->prepare("SELECT DISTINCT user_id FROM user_groups WHERE group_id IN ($gin)");
$peers->execute($my_group_ids);
$peer_ids = array_unique(array_merge($peer_ids, array_column($peers->fetchAll(), 'user_id')));
}
$uid_in = implode(',', array_fill(0, count($peer_ids), '?'));
$stmt = $pdo->prepare("SELECT al.*, u.username FROM activity_log al LEFT JOIN users u ON al.user_id=u.id WHERE al.user_id IN ($uid_in) ORDER BY al.created_at DESC LIMIT 20");
$stmt->execute($peer_ids);
}
$uid_in = implode(',', array_fill(0, count($peer_ids), '?'));
$stmt = $pdo->prepare("SELECT al.*, u.username FROM activity_log al LEFT JOIN users u ON al.user_id=u.id WHERE al.user_id IN ($uid_in) ORDER BY al.created_at DESC LIMIT 20");
$stmt->execute($peer_ids);
$recent_logs = $stmt->fetchAll();
} catch(Exception $e) {}
// ── Auto-log task changes for history (read recent task status changes) ───────
// ── Task logs ─────────────────────────────────────────────────────────────────
$task_logs = [];
try {
// Include group-assigned tasks too
$group_in_tl = $my_group_ids ? implode(',', array_fill(0, count($my_group_ids), '?')) : '0';
$tl_params = array_merge([$user_id, $user_id], $my_group_ids, $my_group_ids);
$stmt = $pdo->prepare("SELECT t.title, t.status, t.status_updated_at, u.username as assignor
FROM tasks t LEFT JOIN users u ON t.assignor_id=u.id
WHERE t.status_updated_at IS NOT NULL
AND (
t.assignee_id=? OR t.assignor_id=?
OR (t.assignee_type='group' AND t.assignee_id IN ($group_in_tl))
OR (t.assignor_type='group' AND t.assignor_group_id IN ($group_in_tl))
)
ORDER BY t.status_updated_at DESC LIMIT 30");
$stmt->execute($tl_params);
if ($is_admin_user) {
// Admin: all recent task status changes
$stmt = $pdo->prepare("SELECT t.title, t.status, t.status_updated_at, u.username as assignor
FROM tasks t LEFT JOIN users u ON t.assignor_id=u.id
WHERE t.status_updated_at IS NOT NULL
ORDER BY t.status_updated_at DESC LIMIT 40");
$stmt->execute();
} else {
$group_in_tl = $my_group_ids ? implode(',', array_fill(0, count($my_group_ids), '?')) : '0';
$tl_params = array_merge([$user_id, $user_id], $my_group_ids, $my_group_ids);
$stmt = $pdo->prepare("SELECT t.title, t.status, t.status_updated_at, u.username as assignor
FROM tasks t LEFT JOIN users u ON t.assignor_id=u.id
WHERE t.status_updated_at IS NOT NULL
AND (
t.assignee_id=? OR t.assignor_id=?
OR (t.assignee_type='group' AND t.assignee_id IN ($group_in_tl))
OR (t.assignor_type='group' AND t.assignor_group_id IN ($group_in_tl))
)
ORDER BY t.status_updated_at DESC LIMIT 30");
$stmt->execute($tl_params);
}
$task_logs = $stmt->fetchAll();
} catch(Exception $e) {}
?>
<style>
.db-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(220px,1fr)); gap:1.25rem; margin-bottom:1.5rem; }
.db-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(200px,1fr)); gap:1.25rem; margin-bottom:1.5rem; }
.db-stat-card {
background:var(--surface); border-radius:1rem; padding:1.25rem 1.5rem;
box-shadow:0 4px 6px -1px rgba(0,0,0,0.07); border:1px solid var(--border);
@@ -103,10 +120,10 @@ try {
text-decoration:none !important; color:var(--text) !important;
}
.db-stat-card:hover { transform:translateY(-3px); box-shadow:0 8px 15px -3px rgba(0,0,0,0.1); }
.db-stat-label { font-size:0.8rem; color:var(--text-light); font-weight:500; text-transform:uppercase; letter-spacing:0.04em; }
.db-stat-label { font-size:0.78rem; color:var(--text-light); font-weight:600; text-transform:uppercase; letter-spacing:0.05em; display:flex; align-items:center; gap:0.4rem; }
.db-stat-value { font-size:2.2rem; font-weight:700; line-height:1; }
.db-stat-sub { display:flex; gap:1rem; flex-wrap:wrap; margin-top:0.35rem; }
.db-stat-sub span { font-size:0.75rem; font-weight:600; padding:2px 8px; border-radius:20px; }
.db-stat-sub { display:flex; gap:0.5rem; flex-wrap:wrap; margin-top:0.35rem; }
.db-stat-sub span { font-size:0.72rem; font-weight:600; padding:2px 8px; border-radius:20px; }
.db-stat-link { font-size:0.78rem; color:var(--primary); margin-top:auto; font-weight:500; }
/* Welcome banner */
@@ -118,21 +135,6 @@ try {
.db-welcome h2 { font-size:1.4rem; font-weight:700; }
.db-welcome p { font-size:0.875rem; opacity:0.85; margin-top:0.25rem; }
/* Holiday strip */
.db-holiday {
background:var(--surface); border:1px solid var(--border); border-radius:0.75rem;
padding:0.75rem 1.25rem; display:flex; align-items:center; gap:0.75rem;
font-size:0.875rem; margin-bottom:1.5rem; overflow:hidden;
}
.db-holiday-list {
display:flex; gap:1.5rem; overflow-x:auto; flex:1; padding-bottom:2px;
scrollbar-width:thin;
}
.db-holiday-item { flex-shrink:0; display:flex; align-items:center; gap:0.4rem; white-space:nowrap; }
.db-holiday-date { font-size:0.7rem; background:rgba(var(--primary-rgb),0.1); color:var(--primary); padding:2px 6px; border-radius:6px; font-weight:600; }
.db-holiday-name { color:var(--text); font-weight:500; }
.db-holiday-country { font-size:0.7rem; color:var(--text-light); }
/* Mood picker */
.mood-row { display:flex; gap:0.4rem; flex-wrap:wrap; align-items:center; }
.mood-btn {
@@ -142,8 +144,8 @@ try {
transition:transform 0.15s, border-color 0.15s, background 0.15s;
line-height:1;
}
.mood-btn:hover { transform:scale(1.15); background:rgba(var(--primary-rgb),0.08); }
.mood-btn.active { border-color:var(--primary); background:rgba(var(--primary-rgb),0.12); transform:scale(1.1); }
.mood-btn:hover { transform:scale(1.15); background:rgba(255,255,255,0.15); }
.mood-btn.active { border-color:rgba(255,255,255,0.9); background:rgba(255,255,255,0.2); transform:scale(1.1); }
/* Team moods */
.team-mood-row { display:flex; flex-wrap:wrap; gap:0.6rem; }
@@ -155,8 +157,8 @@ try {
.team-mood-chip .em { font-size:1.2rem; }
/* Activity log */
.log-list { display:flex; flex-direction:column; gap:0.5rem; overflow-y:auto; flex:1; }
.log-item { display:flex; gap:0.75rem; align-items:flex-start; font-size:0.8rem; flex-shrink:0; }
.log-list { display:flex; flex-direction:column; gap:0.5rem; overflow-y:auto; flex:1; max-height:360px; }
.log-item { display:flex; gap:0.75rem; align-items:flex-start; font-size:0.8rem; flex-shrink:0; padding:0.35rem 0; }
.log-avatar { width:26px; height:26px; border-radius:50%; background:var(--primary); color:#fff; display:flex; align-items:center; justify-content:center; font-size:0.65rem; font-weight:700; flex-shrink:0; }
.log-body { flex:1; min-width:0; }
.log-body strong { color:var(--text); }
@@ -164,16 +166,18 @@ try {
.log-time { font-size:0.7rem; color:var(--text-light); white-space:nowrap; flex-shrink:0; }
/* Full-height bottom section */
.db-wrapper { display:flex; flex-direction:column; min-height:calc(100vh - 4rem); }
.db-wrapper { display:flex; flex-direction:column; }
.two-col {
display:grid; grid-template-columns:1fr 1fr; gap:1.25rem;
flex:1; min-height:0;
}
.two-col > .card {
margin-bottom:0; display:flex; flex-direction:column;
min-height:0; /* allow shrink inside grid */
min-height:0;
}
@media(max-width:720px){ .two-col { grid-template-columns:1fr; } }
.admin-activity-badge { display:inline-flex; align-items:center; gap:0.4rem; font-size:0.7rem; padding:2px 8px; border-radius:12px; background:rgba(239,68,68,0.1); color:#EF4444; font-weight:700; }
</style>
<?php
@@ -225,7 +229,7 @@ if (empty($_SESSION['dash_logged_today'])) {
</div>
</div>
<!-- ── Today's Holiday Badge (injected by JS into welcome banner) ──────────── -->
<!-- ── Today's Holiday Badge ──────────────────────────────────────────────── -->
<div id="today-holiday-banner" style="display:none; margin-bottom:1rem; padding:0.6rem 1.25rem; background:linear-gradient(90deg,rgba(245,158,11,0.12),rgba(16,185,129,0.08)); border:1px solid rgba(245,158,11,0.3); border-radius:0.75rem; align-items:center; gap:0.75rem; font-size:0.875rem;">
<span style="font-size:1.3rem;">🎉</span>
<div>
@@ -322,8 +326,15 @@ if (empty($_SESSION['dash_logged_today'])) {
<!-- Activity Log -->
<div class="card">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
<h3 style="font-size:1rem; font-weight:700;">🕓 Recent Activity</h3>
<span style="font-size:0.75rem; color:var(--text-light);">You & your groups</span>
<div style="display:flex; align-items:center; gap:0.65rem;">
<h3 style="font-size:1rem; font-weight:700;">🕓 Recent Activity</h3>
<?php if($is_admin_user): ?>
<span class="admin-activity-badge"><i class="fa fa-shield-halved" style="font-size:0.65rem;"></i> All Users</span>
<?php endif; ?>
</div>
<span style="font-size:0.75rem; color:var(--text-light);">
<?= $is_admin_user ? 'All activity across platform' : 'You & your groups' ?>
</span>
</div>
<?php
// Merge activity_log + task_logs into one unified feed sorted by time
@@ -346,13 +357,18 @@ if (empty($_SESSION['dash_logged_today'])) {
];
}
usort($feed, fn($a,$b) => strtotime($b['time']) - strtotime($a['time']));
$feed = array_slice($feed, 0, 30);
$feed = array_slice($feed, 0, $is_admin_user ? 50 : 30);
$action_icons = [
'visit' => '🌐',
'mood_set' => '😊',
'task_status' => '✅',
'note_create' => '📝',
'pw_add' => '🔐',
];
// Generate avatar color based on username hash
$avatar_colors = ['#4F46E5','#10B981','#F59E0B','#EF4444','#8B5CF6','#06B6D4','#EC4899','#84CC16'];
?>
<?php if(empty($feed)): ?>
<p style="color:var(--text-light); font-size:0.85rem;">No activity yet.</p>
@@ -362,9 +378,10 @@ if (empty($_SESSION['dash_logged_today'])) {
$initial = strtoupper(mb_substr($entry['user'], 0, 1));
$icon = $action_icons[$entry['action']] ?? '🔔';
$ago = human_time_diff(strtotime($entry['time']));
$color = $avatar_colors[crc32($entry['user']) % count($avatar_colors)];
?>
<div class="log-item">
<div class="log-avatar"><?= $initial ?></div>
<div class="log-avatar" style="background:<?= $color ?>;"><?= $initial ?></div>
<div class="log-body">
<strong><?= htmlspecialchars($entry['user']) ?></strong>
<span class="log-detail"> <?= $icon ?> <?= htmlspecialchars($entry['detail']) ?></span>
@@ -396,7 +413,6 @@ function selectMood(em) {
document.getElementById('mood-value').value = em;
document.querySelectorAll('.mood-btn').forEach(b => b.classList.remove('active'));
if (em) {
// Find the button whose text equals em
document.querySelectorAll('.mood-btn').forEach(b => {
if (b.textContent.trim() === em) b.classList.add('active');
});
@@ -404,10 +420,10 @@ function selectMood(em) {
document.getElementById('mood-form').submit();
}
// ── Holiday check: is TODAY a holiday in Indonesia (ID) or Internationally (UN)?
// ── Holiday check ─────────────────────────────────────────────────────────────
(function checkTodayHoliday() {
const year = new Date().getFullYear();
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
const today = new Date().toISOString().slice(0, 10);
const urls = [
{ url: `https://date.nager.at/api/v3/PublicHolidays/${year}/ID`, flag: '🇮🇩', label: 'Indonesia' },
@@ -434,7 +450,6 @@ function selectMood(em) {
labelEl.textContent = '— Today is a holiday! 🎊';
banner.style.display = 'flex';
}
// If no holiday today, banner stays hidden (display:none)
});
})();
</script>