"use client" import type React from "react" import { useState, useEffect } from "react" import { useRouter } from "next/navigation" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" import { Users, UserCheck, Clock, Search, Filter, ArrowLeft, CheckCircle, XCircle, AlertCircle, Plus, Upload, FileText, Download, Trash2, } from "lucide-react" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import Link from "next/link" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" import { Alert, AlertDescription } from "@/components/ui/alert" import { AuthGuard } from "@/components/auth-guard" import { useAuth } from "@/hooks/use-auth" import { Checkbox } from "@/components/ui/checkbox" import Papa from "papaparse" import * as XLSX from "xlsx" interface User { id: string username: string name: string memberId?: string memberStatus: "verified" | "pending" | "rejected" department?: string joinDate?: string lastLogin?: string } interface MemberStats { totalMembers: number verifiedMembers: number pendingMembers: number rejectedMembers: number votedMembers: number participationRate: number } interface BulkUserData { name: string email: string password: string role: string selected?: boolean rowIndex?: number validationError?: string } interface ParsedFileData { data: BulkUserData[] errors: string[] fileName: string } function MembersPageContent() { const { user } = useAuth("admin") const [members, setMembers] = useState([]) const [stats, setStats] = useState(null) const [searchTerm, setSearchTerm] = useState("") const [statusFilter, setStatusFilter] = useState("all") const [loading, setLoading] = useState(false) const [debugInfo, setDebugInfo] = useState("") const [showCreateUser, setShowCreateUser] = useState(false) const [createUserForm, setCreateUserForm] = useState({ username: "", password: "", name: "", email: "", memberId: "", department: "", joinDate: new Date().toISOString().split("T")[0], }) // Bulk upload states const [showBulkUpload, setShowBulkUpload] = useState(false) const [parsedData, setParsedData] = useState(null) const [bulkLoading, setBulkLoading] = useState(false) const [selectedUsers, setSelectedUsers] = useState>(new Set()) const [selectAll, setSelectAll] = useState(false) const router = useRouter() useEffect(() => { if (user) { loadMembers() loadStats() } }, [user]) const loadMembers = async () => { try { console.log("=== LOADING MEMBERS ===") const response = await fetch("/api/members") const data = await response.json() console.log("Members API response:", data) if (data.success) { setMembers(data.members) console.log("Members loaded:", data.members.length) // Debug info const statusCounts = data.members.reduce((acc: any, member: User) => { acc[member.memberStatus] = (acc[member.memberStatus] || 0) + 1 return acc }, {}) setDebugInfo(`Total: ${data.members.length}, Status: ${JSON.stringify(statusCounts)}`) // Log each member's status data.members.forEach((member: User, index: number) => { console.log( `Member ${index + 1}: ${member.name} - Status: "${member.memberStatus}" (${typeof member.memberStatus})`, ) }) } } catch (error) { console.error("Error loading members:", error) setDebugInfo("Error loading members: " + error.message) } } const loadStats = async () => { try { const response = await fetch("/api/members/stats") const data = await response.json() if (data.success) { setStats(data.stats) } } catch (error) { console.error("Error loading stats:", error) } } const handleStatusChange = async (memberId: string, newStatus: string) => { console.log(`=== CHANGING STATUS ===`) console.log(`Member ID: ${memberId}, New Status: ${newStatus}`) setLoading(true) try { const response = await fetch(`/api/members/${memberId}/status`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: newStatus }), }) const data = await response.json() console.log("Status change response:", data) if (response.ok) { await loadMembers() await loadStats() alert(`Status anggota berhasil diubah menjadi ${newStatus}`) } else { alert("Gagal mengubah status: " + data.message) } } catch (error) { console.error("Error updating member status:", error) alert("Gagal mengubah status anggota") } finally { setLoading(false) } } const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault() setLoading(true) console.log("=== CREATING USER ===") console.log("Form data:", createUserForm) try { const response = await fetch("/api/members", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...createUserForm, role: "voter", memberStatus: "pending", }), }) const data = await response.json() console.log("API response:", data) if (data.success) { console.log("User created successfully:", data.user) // Reload data await loadMembers() await loadStats() // Reset form setCreateUserForm({ username: "", password: "", name: "", email: "", memberId: "", department: "", joinDate: new Date().toISOString().split("T")[0], }) setShowCreateUser(false) alert("Anggota baru berhasil dibuat!") } else { console.error("Failed to create user:", data.message) alert("Gagal membuat anggota: " + data.message) } } catch (error) { console.error("Error creating user:", error) alert("Terjadi kesalahan sistem") } finally { setLoading(false) } } // File parsing functions const validateUserData = (userData: BulkUserData, rowIndex: number): string | null => { if (!userData.name || userData.name.trim().length === 0) { return "Name is required" } if (userData.name.length > 255) { return "Name must be less than 255 characters" } if (!userData.email || !userData.email.includes("@")) { return "Valid email is required" } if (!userData.password || userData.password.length < 6) { return "Password must be at least 6 characters" } if (!userData.role || userData.role.trim().length === 0) { return "Role is required" } return null } const parseCSVFile = (file: File): Promise => { return new Promise((resolve) => { Papa.parse(file, { header: true, skipEmptyLines: true, complete: (result) => { const errors: string[] = [] const data: BulkUserData[] = [] result.data.forEach((row: any, index: number) => { const userData: BulkUserData = { name: row.name || row.Name || "", email: row.email || row.Email || "", password: row.password || row.Password || "", role: row.role || row.Role || "voter", selected: true, rowIndex: index } const validationError = validateUserData(userData, index) if (validationError) { userData.validationError = validationError errors.push(`Row ${index + 2}: ${validationError}`) } data.push(userData) }) resolve({ data, errors, fileName: file.name }) }, error: (error) => { resolve({ data: [], errors: [error.message], fileName: file.name }) } }) }) } const parseExcelFile = (file: File): Promise => { return new Promise((resolve) => { const reader = new FileReader() reader.onload = (e) => { try { const data = new Uint8Array(e.target?.result as ArrayBuffer) const workbook = XLSX.read(data, { type: 'array' }) const sheetName = workbook.SheetNames[0] const worksheet = workbook.Sheets[sheetName] const jsonData = XLSX.utils.sheet_to_json(worksheet) const errors: string[] = [] const users: BulkUserData[] = [] jsonData.forEach((row: any, index: number) => { const userData: BulkUserData = { name: row.name || row.Name || "", email: row.email || row.Email || "", password: row.password || row.Password || "", role: row.role || row.Role || "voter", selected: true, rowIndex: index } const validationError = validateUserData(userData, index) if (validationError) { userData.validationError = validationError errors.push(`Row ${index + 2}: ${validationError}`) } users.push(userData) }) resolve({ data: users, errors, fileName: file.name }) } catch (error) { resolve({ data: [], errors: [error.message], fileName: file.name }) } } reader.readAsArrayBuffer(file) }) } const handleFileUpload = async (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) return setBulkLoading(true) try { let parsed: ParsedFileData if (file.name.toLowerCase().endsWith('.csv')) { parsed = await parseCSVFile(file) } else if (file.name.toLowerCase().endsWith('.xlsx') || file.name.toLowerCase().endsWith('.xls')) { parsed = await parseExcelFile(file) } else { alert('Please upload a CSV or Excel file') setBulkLoading(false) return } setParsedData(parsed) setSelectedUsers(new Set(parsed.data.map((_, index) => index))) setSelectAll(true) } catch (error) { console.error('Error parsing file:', error) alert('Error parsing file: ' + error.message) } finally { setBulkLoading(false) } } const handleSelectAll = (checked: boolean) => { if (checked && parsedData) { setSelectedUsers(new Set(parsedData.data.map((_, index) => index))) } else { setSelectedUsers(new Set()) } setSelectAll(checked) } const handleSelectUser = (index: number, checked: boolean) => { const newSelected = new Set(selectedUsers) if (checked) { newSelected.add(index) } else { newSelected.delete(index) } setSelectedUsers(newSelected) setSelectAll(parsedData ? newSelected.size === parsedData.data.length : false) } const handleBulkCreateUsers = async () => { if (!parsedData || selectedUsers.size === 0) { alert('Please select at least one user to create') return } setBulkLoading(true) try { const selectedUsersData = parsedData.data .filter((_, index) => selectedUsers.has(index)) .filter(user => !user.validationError) // Only include valid users .map(user => ({ name: user.name, email: user.email, password: user.password, role: user.role })) if (selectedUsersData.length === 0) { alert('No valid users selected for creation') setBulkLoading(false) return } const response = await fetch('/api/v1/users/bulk', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ users: selectedUsersData }) }) const result = await response.json() if (response.ok && result.success) { alert(`Successfully created ${selectedUsersData.length} users!`) setParsedData(null) setSelectedUsers(new Set()) setSelectAll(false) setShowBulkUpload(false) // Reload members list await loadMembers() await loadStats() } else { alert(`Failed to create users: ${result.message || 'Unknown error'}`) } } catch (error) { console.error('Error creating bulk users:', error) alert('Error creating users: ' + error.message) } finally { setBulkLoading(false) } } const downloadTemplate = () => { const csvContent = "name,email,password,role\nJohn Doe,john@example.com,password123,voter\nJane Smith,jane@example.com,password456,admin" const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }) const link = document.createElement('a') const url = URL.createObjectURL(blob) link.setAttribute('href', url) link.setAttribute('download', 'user_template.csv') link.style.visibility = 'hidden' document.body.appendChild(link) link.click() document.body.removeChild(link) } const filteredMembers = members.filter((member) => { const matchesSearch = member.name.toLowerCase().includes(searchTerm.toLowerCase()) || member.memberId?.toLowerCase().includes(searchTerm.toLowerCase()) || member.department?.toLowerCase().includes(searchTerm.toLowerCase()) const matchesStatus = statusFilter === "all" || member.memberStatus === statusFilter return matchesSearch && matchesStatus }) const getStatusBadge = (status: string) => { switch (status) { case "verified": return ( Terverifikasi ) case "pending": return ( Menunggu ) case "rejected": return ( Ditolak ) default: return ( Status Tidak Valid: {status} ) } } if (!user) return null return (
METI - New & Renewable Energy

Verifikasi Anggota

{/* Debug Info */} {debugInfo && ( Debug Info: {debugInfo} )} {/* Statistics Cards */} {stats && (
Total Anggota
{stats.totalMembers}
Terverifikasi
{stats.verifiedMembers}
Menunggu
{stats.pendingMembers}
Partisipasi
{stats.participationRate}%

{stats.votedMembers} dari {stats.verifiedMembers}

)} {/* Create User & Filters */}
Kelola Anggota
Tambah Anggota Baru Buat akun voter baru untuk anggota METI
setCreateUserForm({ ...createUserForm, username: e.target.value })} placeholder="username_anggota" required />
setCreateUserForm({ ...createUserForm, password: e.target.value })} placeholder="Password untuk login" required />
setCreateUserForm({ ...createUserForm, name: e.target.value })} placeholder="Nama lengkap anggota" required />
setCreateUserForm({ ...createUserForm, email: e.target.value })} placeholder="email@example.com" required />
setCreateUserForm({ ...createUserForm, memberId: e.target.value })} placeholder="METI-XXX" required />
setCreateUserForm({ ...createUserForm, department: e.target.value })} placeholder="Renewable Energy, Solar Energy, dll" required />
setCreateUserForm({ ...createUserForm, joinDate: e.target.value })} required />
Upload Bulk Users Upload CSV or Excel file to create multiple users at once
{!parsedData ? (
{/* File Upload Section */}

Upload File

Choose a CSV or Excel file containing user data

{/* Template Download */}

Need a template?

Download our CSV template to get started. Required columns: name, email, password, role

{/* Format Info */}

File Format Requirements:

  • name: Full name (required, max 255 chars)
  • email: Valid email address (required)
  • password: Password (required, min 6 chars)
  • role: User role (required, e.g., 'voter', 'admin')
) : (
{/* File Info & Actions */}

Preview: {parsedData.fileName}

Found {parsedData.data.length} users, {selectedUsers.size} selected

{/* Errors Display */} {parsedData.errors.length > 0 && ( Validation Errors:
    {parsedData.errors.slice(0, 5).map((error, index) => (
  • • {error}
  • ))} {parsedData.errors.length > 5 && (
  • • ... and {parsedData.errors.length - 5} more errors
  • )}
)} {/* Data Table */}
handleSelectAll(checked || false)} /> Name Email Role Status {parsedData.data.map((user, index) => ( handleSelectUser(index, checked || false)} disabled={!!user.validationError} /> {user.name || Missing} {user.email || Missing} {user.role || Missing} {user.validationError ? ( {user.validationError} ) : ( Valid )} ))}
)}
setSearchTerm(e.target.value)} className="pl-10" />
{/* Members Table */} Daftar Anggota ({filteredMembers.length}) Kelola verifikasi dan status keanggotaan METI {/* Desktop Table View */}
ID Anggota Username Nama Departemen Tanggal Bergabung Status Login Terakhir Aksi {filteredMembers.map((member) => ( {member.memberId} {member.username} {member.name} {member.department} {member.joinDate ? new Date(member.joinDate).toLocaleDateString("id-ID") : "-"} {getStatusBadge(member.memberStatus)} {member.lastLogin ? new Date(member.lastLogin).toLocaleDateString("id-ID") : "Belum pernah login"}
{/* Existing button logic remains the same */} {member.memberStatus === "pending" && ( <> Konfirmasi Verifikasi Apakah Anda yakin ingin memverifikasi anggota berikut?

Nama: {member.name}

ID Anggota: {member.memberId}

Departemen: {member.department}

Status Saat Ini: Menunggu Verifikasi

Konfirmasi Penolakan Apakah Anda yakin ingin menolak verifikasi anggota berikut?

Nama: {member.name}

ID Anggota: {member.memberId}

Departemen: {member.department}

Aksi: Anggota akan ditolak dan tidak dapat login

)} {/* Other status buttons remain the same */} {member.memberStatus === "rejected" && ( Konfirmasi Verifikasi Anggota ini sebelumnya ditolak. Apakah Anda yakin ingin memverifikasinya sekarang?

Nama: {member.name}

ID Anggota: {member.memberId}

Status Saat Ini: Ditolak

Aksi: Anggota akan dapat login dan voting

)} {/* Button untuk status verified */} {member.memberStatus === "verified" && ( Konfirmasi Penangguhan Apakah Anda yakin ingin menangguhkan verifikasi anggota ini?

Nama: {member.name}

ID Anggota: {member.memberId}

Status Saat Ini:{" "} Terverifikasi

Aksi: Anggota tidak akan dapat login sementara

)} {/* Debug info - hapus ini setelah testing */} Status: {member.memberStatus}
))}
{/* Mobile Card View */}
{filteredMembers.map((member) => (

{member.name}

{member.memberId}

@{member.username}

{getStatusBadge(member.memberStatus)}
Departemen:

{member.department}

Bergabung:

{member.joinDate ? new Date(member.joinDate).toLocaleDateString("id-ID") : "-"}

Login Terakhir:

{member.lastLogin ? new Date(member.lastLogin).toLocaleDateString("id-ID") : "Belum pernah login"}

{/* Mobile Action Buttons */}
{member.memberStatus === "pending" && ( <> Konfirmasi Verifikasi Apakah Anda yakin ingin memverifikasi anggota berikut?

Nama: {member.name}

ID Anggota: {member.memberId}

Departemen: {member.department}

Status Saat Ini: Menunggu Verifikasi

Konfirmasi Penolakan Apakah Anda yakin ingin menolak verifikasi anggota berikut?

Nama: {member.name}

ID Anggota: {member.memberId}

Departemen: {member.department}

Aksi: Anggota akan ditolak dan tidak dapat login

)} {member.memberStatus === "rejected" && ( Konfirmasi Verifikasi Anggota ini sebelumnya ditolak. Apakah Anda yakin ingin memverifikasinya sekarang?

Nama: {member.name}

ID Anggota: {member.memberId}

Status Saat Ini: Ditolak

Aksi: Anggota akan dapat login dan voting

)} {member.memberStatus === "verified" && ( Konfirmasi Penangguhan Apakah Anda yakin ingin menangguhkan verifikasi anggota ini?

Nama: {member.name}

ID Anggota: {member.memberId}

Status Saat Ini:{" "} Terverifikasi

Aksi: Anggota tidak akan dapat login sementara

)}
))}
{filteredMembers.length === 0 && (

Tidak ada anggota ditemukan

Coba ubah filter atau kata kunci pencarian

)}
) } export default function MembersPage() { return ( ) }