File: /var/www/indoadvisory_new/webapp/src/routes/team.js
import { Hono } from 'hono';
const team = new Hono();
// Team Members List
team.get('/', async (c) => {
try {
const teamList = await c.env.DB.prepare('SELECT * FROM team_members ORDER BY sort_order ASC, created_at ASC').all();
const user = c.get('user');
return c.html(`
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kelola Tim - Admin IndoAdvisory</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
${adminNavbar(user)}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold text-gray-800">Kelola Tim Ahli</h1>
<a href="/admin/team/create" class="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors">
<i class="fas fa-plus mr-2"></i>
Tambah Anggota Tim
</a>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
${teamList.results.map(member => `
<div class="bg-white rounded-xl shadow-lg overflow-hidden ${!member.is_active ? 'opacity-60' : ''}">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<div class="w-16 h-16 bg-gradient-to-br from-blue-400 to-blue-600 rounded-full flex items-center justify-center">
${member.image_url ?
`<img src="${member.image_url}" alt="${member.name}" class="w-16 h-16 rounded-full object-cover">` :
`<i class="fas fa-user text-white text-xl"></i>`}
</div>
<div class="text-right">
<span class="text-xs font-medium px-2 py-1 rounded-full ${member.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'}">
${member.is_active ? 'Aktif' : 'Nonaktif'}
</span>
</div>
</div>
<h3 class="text-xl font-bold text-gray-900 mb-2">${member.name}</h3>
<p class="text-blue-600 font-medium mb-3">${member.position_id}</p>
${member.bio_id ? `
<p class="text-gray-600 text-sm mb-4 line-clamp-3">${member.bio_id}</p>
` : ''}
<div class="flex items-center justify-between">
<div class="flex space-x-2">
${member.email ? `
<a href="mailto:${member.email}" class="text-gray-500 hover:text-blue-600">
<i class="fas fa-envelope"></i>
</a>
` : ''}
${member.linkedin_url ? `
<a href="${member.linkedin_url}" target="_blank" class="text-gray-500 hover:text-blue-600">
<i class="fab fa-linkedin"></i>
</a>
` : ''}
</div>
<div class="flex space-x-2">
<a href="/admin/team/edit/${member.id}" class="text-blue-600 hover:text-blue-900">
<i class="fas fa-edit"></i>
</a>
<button onclick="toggleStatus(${member.id}, ${member.is_active})" class="text-${member.is_active ? 'orange' : 'green'}-600 hover:text-${member.is_active ? 'orange' : 'green'}-900">
<i class="fas fa-${member.is_active ? 'pause' : 'play'}"></i>
</button>
<button onclick="deleteMember(${member.id})" class="text-red-600 hover:text-red-900">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="mt-4 text-center">
<span class="text-xs text-gray-500">Urutan: ${member.sort_order}</span>
</div>
</div>
</div>
`).join('')}
${teamList.results.length === 0 ? `
<div class="col-span-full text-center py-16">
<i class="fas fa-users text-gray-400 text-6xl mb-4"></i>
<h3 class="text-xl font-semibold text-gray-600 mb-2">Belum Ada Anggota Tim</h3>
<p class="text-gray-500 mb-6">Tambahkan anggota tim pertama untuk mulai membangun profil perusahaan</p>
<a href="/admin/team/create" class="bg-blue-600 text-white px-6 py-3 rounded-lg font-semibold hover:bg-blue-700 transition-colors">
<i class="fas fa-plus mr-2"></i>
Tambah Anggota Tim
</a>
</div>
` : ''}
</div>
</div>
<script>
function toggleStatus(id, currentStatus) {
const newStatus = !currentStatus;
fetch('/admin/team/' + id + '/toggle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ is_active: newStatus })
}).then(() => window.location.reload());
}
function deleteMember(id) {
if (confirm('Apakah Anda yakin ingin menghapus anggota tim ini?')) {
fetch('/admin/team/' + id + '/delete', { method: 'POST' })
.then(() => window.location.reload());
}
}
</script>
</body>
</html>
`);
}
catch (error) {
return c.html(`<div>Error: ${error.message}</div>`);
}
});
// Create Team Member Form
team.get('/create', (c) => {
const user = c.get('user');
return c.html(`
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tambah Anggota Tim - Admin IndoAdvisory</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100">
${adminNavbar(user)}
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-8">
<h1 class="text-3xl font-bold text-gray-800">Tambah Anggota Tim</h1>
<p class="text-gray-600 mt-2">Tambahkan profil anggota tim baru</p>
</div>
<form action="/admin/team/create" method="POST" class="bg-white rounded-xl shadow-lg p-8 space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Nama Lengkap</label>
<input
type="text"
name="name"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Dr. Budi Santoso"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input
type="email"
name="email"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="budi.santoso@indoadvisory.co.id"
/>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Posisi (Bahasa Indonesia)</label>
<input
type="text"
name="position_id"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Managing Partner"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Posisi (English)</label>
<input
type="text"
name="position_en"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Managing Partner"
/>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bio/Pengalaman (Bahasa Indonesia)</label>
<textarea
name="bio_id"
rows="4"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Pengalaman dan latar belakang pendidikan..."
></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Bio/Experience (English)</label>
<textarea
name="bio_en"
rows="4"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Experience and educational background..."
></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">LinkedIn URL</label>
<input
type="url"
name="linkedin_url"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="https://linkedin.com/in/username"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">URL Foto Profil</label>
<input
type="url"
name="image_url"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="https://example.com/foto-profil.jpg"
/>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Urutan Tampil</label>
<input
type="number"
name="sort_order"
min="0"
value="0"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<p class="text-sm text-gray-500 mt-1">Semakin kecil angka, semakin atas urutan tampil</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Status</label>
<select
name="is_active"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="1">Aktif</option>
<option value="0">Nonaktif</option>
</select>
</div>
</div>
<div class="flex justify-between items-center pt-6">
<a href="/admin/team" class="px-6 py-3 text-gray-700 bg-gray-200 rounded-lg hover:bg-gray-300 transition-colors">
<i class="fas fa-arrow-left mr-2"></i>
Kembali
</a>
<button
type="submit"
class="px-8 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors"
>
<i class="fas fa-save mr-2"></i>
Simpan Anggota Tim
</button>
</div>
</form>
</div>
</body>
</html>
`);
});
// Create Team Member Handler
team.post('/create', async (c) => {
try {
const formData = await c.req.parseBody();
const now = new Date().toISOString();
await c.env.DB.prepare(`INSERT INTO team_members (name, position_id, position_en, bio_id, bio_en, email, linkedin_url, image_url, sort_order, is_active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).bind(formData.name, formData.position_id, formData.position_en, formData.bio_id || null, formData.bio_en || null, formData.email || null, formData.linkedin_url || null, formData.image_url || null, parseInt(formData.sort_order) || 0, parseInt(formData.is_active) === 1, now, now).run();
return c.redirect('/admin/team');
}
catch (error) {
return c.html(`<script>alert('Error: ${error.message}'); window.history.back();</script>`);
}
});
// Toggle team member status
team.post('/:id/toggle', async (c) => {
try {
const id = c.req.param('id');
const { is_active } = await c.req.json();
await c.env.DB.prepare('UPDATE team_members SET is_active = ?, updated_at = ? WHERE id = ?').bind(is_active, new Date().toISOString(), id).run();
return c.json({ success: true });
}
catch (error) {
return c.json({ error: error.message }, 500);
}
});
// Delete team member
team.post('/:id/delete', async (c) => {
try {
const id = c.req.param('id');
await c.env.DB.prepare('DELETE FROM team_members WHERE id = ?').bind(id).run();
return c.json({ success: true });
}
catch (error) {
return c.json({ error: error.message }, 500);
}
});
// Admin Navbar Component
function adminNavbar(user) {
return `
<nav class="bg-white shadow-lg border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<div class="flex items-center">
<h1 class="text-xl font-bold text-gray-800">
<i class="fas fa-chart-line text-blue-600 mr-2"></i>
IndoAdvisory Admin
</h1>
</div>
<div class="hidden md:flex items-center space-x-6">
<a href="/admin/dashboard" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-tachometer-alt mr-1"></i>
Dashboard
</a>
<a href="/admin/articles" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-newspaper mr-1"></i>
Artikel
</a>
<a href="/admin/inquiries" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-envelope mr-1"></i>
Inquiry
</a>
<a href="/admin/team" class="text-blue-600 font-semibold px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-users mr-1"></i>
Tim
</a>
<a href="/admin/settings" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-cog mr-1"></i>
Settings
</a>
<a href="/" target="_blank" class="text-gray-700 hover:text-blue-600 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-external-link-alt mr-1"></i>
Lihat Website
</a>
</div>
<div class="flex items-center">
<span class="text-gray-700 mr-4">
<i class="fas fa-user mr-1"></i>
${user.name}
</span>
<a href="/admin/logout" class="text-red-600 hover:text-red-700 px-3 py-2 rounded-md text-sm font-medium">
<i class="fas fa-sign-out-alt mr-1"></i>
Logout
</a>
</div>
</div>
</div>
</nav>
`;
}
export default team;