"use client" 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, Loader2, Upload, } 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 { useToast } from "@/hooks/use-toast" import apiClient from "@/lib/api-client" import * as XLSX from "xlsx" import { Checkbox } from "@/components/ui/checkbox" interface User { id: string username: string name: string email: string is_active: boolean created_at: string updated_at: string roles?: Array<{ id: string name: string code: string }> | null department_response?: { name: string } | null profile?: { user_id: string full_name: string timezone: string locale: string preferences: any notification_prefs: any created_at: string updated_at: string roles: any } | null } interface ApiResponse { success: boolean data: { users: User[] pagination: { total_count: number page: number limit: number total_pages: number } } errors: any } interface BulkUserRequest { name: string email: string password: string role: string } interface BulkCreateUsersRequest { users: BulkUserRequest[] } interface BulkCreateAsyncResponse { job_id: string message: string status: string } interface BulkJobResult { job_id: string status: string message: string started_at: string finished_at?: string summary: { total: number succeeded: number failed: number } created: Array<{ id: string name: string email: string is_active: boolean created_at: string updated_at: string roles: Array<{ id: string name: string code: string }> department_response: Array<{ name: string }> }> failed: Array<{ user: BulkUserRequest error: string }> } interface ExcelUser { Nama: string Password: string Email: string Role: string } function MembersPageContent() { const { user } = useAuth("admin") const { toast } = useToast() const router = useRouter() const [users, setUsers] = useState([]) const [loading, setLoading] = useState(false) const [searchTerm, setSearchTerm] = useState("") const [statusFilter, setStatusFilter] = useState("all") // Pagination states const [currentPage, setCurrentPage] = useState(1) const [pageSize, setPageSize] = useState(10) const [totalUsers, setTotalUsers] = useState(0) const [paginationLoading, setPaginationLoading] = useState(false) // Bulk upload states const [showBulkUpload, setShowBulkUpload] = useState(false) const [bulkUsers, setBulkUsers] = useState([]) const [selectedUsers, setSelectedUsers] = useState>(new Set()) const [bulkLoading, setBulkLoading] = useState(false) const [selectAll, setSelectAll] = useState(false) // Job tracking states const [currentJobId, setCurrentJobId] = useState(null) const [jobStatus, setJobStatus] = useState(null) const [jobLoading, setJobLoading] = useState(false) const [showJobStatus, setShowJobStatus] = useState(false) useEffect(() => { if (user) { fetchUsers(1, pageSize) // Always start from first page loadStoredJob() } }, [user]) // Reset search when changing pages since search is limited to current page useEffect(() => { if (user && currentPage > 1) { setSearchTerm("") } }, [currentPage]) // Reset to first page when filters change useEffect(() => { if (user && (searchTerm || statusFilter !== "all")) { setCurrentPage(1) fetchUsers(1, pageSize) } }, [searchTerm, statusFilter]) const fetchUsers = async (page: number = currentPage, size: number = pageSize) => { try { setLoading(true) const response = await apiClient.get(`/api/v1/users?page=${page}&limit=${size}`) const data: ApiResponse = response.data if (data.success) { setUsers(data.data.users) setTotalUsers(data.data.pagination.total_count) setCurrentPage(data.data.pagination.page) console.log('📊 Pagination Info:', { currentPage: data.data.pagination.page, totalCount: data.data.pagination.total_count, totalPages: data.data.pagination.total_pages, limit: data.data.pagination.limit }) toast({ title: "Success", description: `Fetched ${data.data.users.length} users (Page ${data.data.pagination.page} of ${data.data.pagination.total_pages})`, }) } else { toast({ title: "Error", description: "Failed to fetch users", variant: "destructive" }) } } catch (error: any) { console.error('Error fetching users:', error) toast({ title: "Error", description: error.response?.data?.errors || "Failed to fetch users from API", variant: "destructive" }) } finally { setLoading(false) setPaginationLoading(false) } } const getStatusBadge = (isActive: boolean) => { if (isActive) { return ( Active ) } else { return ( Inactive ) } } const getRoleBadge = (roles: User['roles']) => { if (roles && roles.length > 0) { return ( {roles[0].name} ) } return ( No Role ) } const handleFileUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0] if (!file) return 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) as ExcelUser[] // Transform Excel data to API format const transformedUsers: BulkUserRequest[] = jsonData.map((row, index) => ({ name: row.Nama?.trim() || '', email: row.Email?.trim() || '', password: row.Password?.trim() || '', role: row.Role?.trim() || 'Staff' })).filter(user => user.name && user.email && user.password) setBulkUsers(transformedUsers) setSelectedUsers(new Set()) setSelectAll(false) setShowBulkUpload(true) toast({ title: "File Uploaded", description: `Processed ${transformedUsers.length} users from Excel file`, }) } catch (error) { console.error('Error processing Excel file:', error) toast({ title: "Error", description: "Failed to process Excel file. Please check the format.", variant: "destructive" }) } } reader.readAsArrayBuffer(file) } const handleSelectUser = (index: number) => { const newSelected = new Set(selectedUsers) if (newSelected.has(index)) { newSelected.delete(index) } else { newSelected.add(index) } setSelectedUsers(newSelected) } const handleSelectAll = () => { if (selectAll) { setSelectedUsers(new Set()) setSelectAll(false) } else { setSelectedUsers(new Set(bulkUsers.map((_, index) => index))) setSelectAll(true) } } const handleBulkCreate = async () => { if (selectedUsers.size === 0) { toast({ title: "No Users Selected", description: "Please select at least one user to create", variant: "destructive" }) return } const selectedUserData = Array.from(selectedUsers).map(index => bulkUsers[index]) try { setBulkLoading(true) const response = await apiClient.post('/api/v1/users/bulk', { users: selectedUserData }) if (response.data.success) { const jobData: BulkCreateAsyncResponse = response.data.data // Store job ID in localStorage localStorage.setItem("bulk_job_id", jobData.job_id) setCurrentJobId(jobData.job_id) toast({ title: "Bulk Upload Started", description: `Job ${jobData.job_id} created. You can track progress below.`, }) // Show job status dialog setShowJobStatus(true) setShowBulkUpload(false) // Start polling for job status setTimeout(() => { checkJobStatus(jobData.job_id) }, 2000) } else { toast({ title: "Error", description: response.data.errors || "Failed to create users", variant: "destructive" }) } } catch (error: any) { console.error('Error creating bulk users:', error) toast({ title: "Error", description: error.response?.data?.errors || "Failed to create users", variant: "destructive" }) } finally { setBulkLoading(false) } } const checkJobStatus = async (jobId: string) => { try { setJobLoading(true) const response = await apiClient.get(`/api/v1/users/bulk/job/${jobId}`) if (response.data.success) { const jobResult: BulkJobResult = response.data.data setJobStatus(jobResult) // If job is completed, refresh users list if (jobResult.status === 'completed' || jobResult.status === 'failed') { await fetchUsers(currentPage, pageSize) // Show completion message if (jobResult.status === 'completed') { toast({ title: "Bulk Upload Completed", description: `Successfully created ${jobResult.summary.succeeded} users. ${jobResult.summary.failed} failed.`, }) } else { toast({ title: "Bulk Upload Failed", description: `Failed to create users: ${jobResult.message}`, variant: "destructive" }) } } else { // Continue polling if job is still running setTimeout(() => { checkJobStatus(jobId) }, 5000) } } } catch (error: any) { console.error('Error checking job status:', error) toast({ title: "Error", description: "Failed to check job status", variant: "destructive" }) } finally { setJobLoading(false) } } const loadStoredJob = () => { const storedJobId = localStorage.getItem("bulk_job_id") if (storedJobId) { setCurrentJobId(storedJobId) setShowJobStatus(true) checkJobStatus(storedJobId) } } // Pagination functions const handlePageChange = (page: number) => { setPaginationLoading(true) setCurrentPage(page) fetchUsers(page, pageSize) } const handlePageSizeChange = (size: number) => { setPaginationLoading(true) setPageSize(size) setCurrentPage(1) // Reset to first page when changing page size fetchUsers(1, size) } const totalPages = Math.ceil(totalUsers / pageSize) const startIndex = (currentPage - 1) * pageSize + 1 const endIndex = Math.min(currentPage * pageSize, totalUsers) const filteredUsers = users.filter((user) => { const matchesSearch = user.name.toLowerCase().includes(searchTerm.toLowerCase()) || user.username.toLowerCase().includes(searchTerm.toLowerCase()) || user.email.toLowerCase().includes(searchTerm.toLowerCase()) || (user.department_response?.name || "").toLowerCase().includes(searchTerm.toLowerCase()) const matchesStatus = statusFilter === "all" || (statusFilter === "active" && user.is_active) || (statusFilter === "inactive" && !user.is_active) return matchesSearch && matchesStatus }) if (!user) return null return (
{/* Header */}

User Management

{/* Statistics */}
Total Users
{totalUsers}
Active Users
{users.filter(u => u.is_active).length} / {totalUsers}
Inactive Users
{users.filter(u => !u.is_active).length} / {totalUsers}
Departments
{new Set(users.map(u => u.department_response?.name).filter(Boolean)).size}
{/* Filters and Search */} Filters & Search
setSearchTerm(e.target.value)} className="pl-10" />

Search is limited to the current page. Use pagination to view more users.

{currentJobId && ( )}
{/* Users Table */} Users List Showing {startIndex}-{endIndex} of {totalUsers} total users {filteredUsers.length !== users.length && ` (${filteredUsers.length} filtered from current page)`} {loading ? (

Loading users...

) : filteredUsers.length === 0 ? (

No users found

Try adjusting your search or filters

) : (
Username Name Email Department Role Status Created {filteredUsers.map((user) => ( {user.username} {user.name} {user.email} {user.department_response?.name || "N/A"} {getRoleBadge(user.roles)} {getStatusBadge(user.is_active)} {new Date(user.created_at).toLocaleDateString()} ))}
)} {/* Pagination Controls */} {totalPages > 1 && (
{paginationLoading && (
)}
Page {currentPage} of {totalPages} {startIndex}-{endIndex} of {totalUsers} results
{/* Previous Page */} {/* Page Numbers */}
{/* First Page */} {currentPage > 3 && ( <> {currentPage > 4 && ( ... )} )} {/* Page Range */} {Array.from({ length: Math.min(5, totalPages) }, (_, i) => { const page = Math.max(1, Math.min(totalPages - 4, currentPage - 2)) + i if (page > totalPages) return null return ( ) })} {/* Last Page */} {currentPage < totalPages - 2 && ( <> {currentPage < totalPages - 3 && ( ... )} )}
{/* Next Page */}
)}
{/* Bulk Upload Dialog */} {showBulkUpload && ( Bulk User Upload Review and select users from the uploaded Excel file. Selected users will be created in the system.
{/* Bulk Actions */}
{selectedUsers.size} of {bulkUsers.length} selected
{/* Users Table */}
Select Name Email Password Role {bulkUsers.map((user, index) => ( handleSelectUser(index)} /> {user.name} {user.email} {user.password.length > 8 ? `${user.password.substring(0, 8)}...` : user.password} {user.role} ))}
{/* Instructions */}

Excel Format Requirements:

  • Nama: User's full name (required)
  • Password: User's password (min 6 characters)
  • Email: Valid email address (required)
  • Role: User role (defaults to "Staff" if empty)

Note: Bulk user creation is processed asynchronously. You'll receive a job ID to track progress.

)} {/* Job Status Dialog */} {showJobStatus && ( Bulk Upload Job Status {currentJobId && `Job ID: ${currentJobId}`}
{jobLoading ? (

Checking job status...

) : jobStatus ? ( <> {/* Job Summary */} Job Summary
{jobStatus.summary.total}
Total Users
{jobStatus.summary.succeeded}
Succeeded
{jobStatus.summary.failed}
Failed
{jobStatus.status === 'completed' ? '✅' : jobStatus.status === 'failed' ? '❌' : '⏳'}
{jobStatus.status}
Started: {new Date(jobStatus.started_at).toLocaleString()}
{jobStatus.finished_at && (
Finished: {new Date(jobStatus.finished_at).toLocaleString()}
)}
Message: {jobStatus.message}
{/* Created Users */} {jobStatus.created.length > 0 && ( Successfully Created Users
Name Email Status Created {jobStatus.created.map((user) => ( {user.name} {user.email} Active {new Date(user.created_at).toLocaleDateString()} ))}
)} {/* Failed Users */} {jobStatus.failed.length > 0 && ( Failed Users
Name Email Role Error {jobStatus.failed.map((failedUser, index) => ( {failedUser.user.name} {failedUser.user.email} {failedUser.user.role} {failedUser.error} ))}
)} {/* Action Buttons */}
{jobStatus.status === 'completed' && ( )} {jobStatus.status !== 'completed' && jobStatus.status !== 'failed' && ( )}
) : (

No job status available

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