Initial commit

This commit is contained in:
2026-08-07 23:53:15 +05:30
commit 90f14a73b2
222 changed files with 35000 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
import React, { useState } from 'react';
import { Search } from 'lucide-react';
const EVENT_LABELS = {
signup: 'Sign Up',
login: 'Login',
logout: 'Logout',
create_position: 'Create Position',
screen_candidate: 'Screen Candidate',
hire_candidate: 'Hire Candidate',
apply_job: 'Apply to Job',
start_interview: 'Start Interview',
};
const EVENT_COLORS = {
signup: 'bg-[#EFF6FF] text-[#1E40AF]',
login: 'bg-[#ECFDF5] text-[#065F46]',
logout: 'bg-[#F3F4F6] text-[#374151]',
create_position: 'bg-[#F5F3FF] text-[#5B21B6]',
screen_candidate: 'bg-[#FFFBEB] text-[#92400E]',
hire_candidate: 'bg-[#ECFDF5] text-[#065F46]',
apply_job: 'bg-[#EFF6FF] text-[#1E40AF]',
start_interview: 'bg-[#FEF2F2] text-[#991B1B]',
};
function formatTime(dateStr) {
if (!dateStr) return '';
const d = new Date(dateStr);
return d.toLocaleString('en-US', {
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit',
});
}
export default function ActivityLogTable({ activities }) {
const [filter, setFilter] = useState('all');
const [search, setSearch] = useState('');
const filtered = activities.filter((a) => {
if (filter !== 'all' && a.event_type !== filter) return false;
if (search) {
const q = search.toLowerCase();
return (
(a.user_email || '').toLowerCase().includes(q) ||
(a.user_name || '').toLowerCase().includes(q)
);
}
return true;
});
const eventTypes = ['all', ...Object.keys(EVENT_LABELS)];
return (
<div className="bg-white rounded-xl border border-[#E5E7EB] overflow-hidden">
<div className="p-4 border-b border-[#E5E7EB] flex flex-col sm:flex-row gap-3 sm:items-center justify-between">
<h3 className="text-[14px] font-semibold text-[#111827]">Activity Log</h3>
<div className="flex gap-2">
<div className="relative">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-[#9CA3AF]" />
<input
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search user..."
className="pl-8 pr-3 h-8 text-[12px] rounded-md border border-[#E5E7EB] focus:border-[#2563EB] focus:outline-none w-40"
/>
</div>
<select
value={filter}
onChange={(e) => setFilter(e.target.value)}
className="h-8 text-[12px] rounded-md border border-[#E5E7EB] bg-white px-2 focus:border-[#2563EB] focus:outline-none"
>
{eventTypes.map((t) => (
<option key={t} value={t}>
{t === 'all' ? 'All Events' : EVENT_LABELS[t]}
</option>
))}
</select>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-[13px]">
<thead className="bg-[#F9FAFB] text-[#6B7280]">
<tr>
<th className="text-left font-medium px-4 py-2.5">Event</th>
<th className="text-left font-medium px-4 py-2.5">User</th>
<th className="text-left font-medium px-4 py-2.5">Role</th>
<th className="text-left font-medium px-4 py-2.5">Details</th>
<th className="text-left font-medium px-4 py-2.5">Time</th>
</tr>
</thead>
<tbody className="divide-y divide-[#F3F4F6]">
{filtered.length === 0 ? (
<tr>
<td colSpan={5} className="text-center py-8 text-[#9CA3AF]">
No activity found
</td>
</tr>
) : (
filtered.slice(0, 50).map((a) => (
<tr key={a.id} className="hover:bg-[#F9FAFB]">
<td className="px-4 py-2.5">
<span
className={`inline-block px-2 py-0.5 rounded-full text-[11px] font-medium ${
EVENT_COLORS[a.event_type] || 'bg-[#F3F4F6] text-[#374151]'
}`}
>
{EVENT_LABELS[a.event_type] || a.event_type}
</span>
</td>
<td className="px-4 py-2.5">
<div className="font-medium text-[#111827]">{a.user_name || '—'}</div>
<div className="text-[11px] text-[#9CA3AF]">{a.user_email}</div>
</td>
<td className="px-4 py-2.5 capitalize text-[#6B7280]">{a.account_type || '—'}</td>
<td className="px-4 py-2.5 text-[#6B7280] max-w-[200px] truncate">
{a.details || '—'}
</td>
<td className="px-4 py-2.5 text-[#6B7280] whitespace-nowrap">
{formatTime(a.created_date)}
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
);
}