'use client'
import { useActionState, useCallback, useEffect, useState, useTransition } from 'react'
import Swal, { type SweetAlertIcon } from 'sweetalert2'
import {
createUser,
updateUser,
deleteUser,
changeUserStatus,
type User,
type UserFormState,
type UsersResponse,
} from './actions'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Checkbox } from '@/components/ui/checkbox'
import { Badge } from '@/components/ui/badge'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter,
DialogDescription,
} from '@/components/ui/dialog'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import { Loader2, Plus, Pencil, Trash2, AlertTriangle } from 'lucide-react'
type Props = { initialData: UsersResponse }
type ModalMode = 'create' | 'edit' | null
function getSwalThemeOptions() {
const isDark = document.documentElement.classList.contains('dark')
return isDark
? { background: '#111827', color: '#f3f4f6' }
: { background: '#ffffff', color: '#111827' }
}
function showToast(icon: SweetAlertIcon, title: string) {
void Swal.fire({
...getSwalThemeOptions(),
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 2200,
timerProgressBar: true,
icon,
title,
})
}
/* ─── Field ─────────────────────────────────────────── */
function Field({ id, label, children }: { id?: string; label: string; children: React.ReactNode }) {
return (
{children}
)
}
/* ─── UserModal ─────────────────────────────────────── */
function UserModal({
open,
mode,
user,
onSaved,
onClose,
}: {
open: boolean
mode: 'create' | 'edit'
user?: User
onSaved: (savedUser: User, mode: 'create' | 'edit') => void
onClose: () => void
}) {
const isEdit = mode === 'edit' && user != null
const boundAction = isEdit ? updateUser.bind(null, user.id) : createUser
const [state, formAction, pending] = useActionState(boundAction, {})
useEffect(() => {
if (!state.success) return
if (state.user) onSaved(state.user, mode)
showToast('success', mode === 'edit' ? 'Kullanıcı güncellendi' : 'Kullanıcı oluşturuldu')
onClose()
}, [state.success, state.user, mode, onSaved, onClose])
useEffect(() => {
if (!state.error) return
showToast('error', state.error)
}, [state.error])
return (
)
}
/* ─── UsersClient ────────────────────────────────────── */
export default function UsersClient({ initialData }: Props) {
const [data, setData] = useState(initialData)
const [modal, setModal] = useState(null)
const [selectedUser, setSelectedUser] = useState()
const [, startTransition] = useTransition()
const openCreate = useCallback(() => {
setSelectedUser(undefined)
setModal('create')
}, [])
const openEdit = useCallback((user: User) => {
setSelectedUser(user)
setModal('edit')
}, [])
const closeModal = useCallback(() => {
setModal(null)
setSelectedUser(undefined)
}, [])
const handleUserSaved = useCallback((savedUser: User, mode: 'create' | 'edit') => {
setData((prev) => {
const existingIndex = prev.items.findIndex((u) => u.id === savedUser.id)
if (mode === 'edit' || existingIndex >= 0) {
return {
...prev,
items: prev.items.map((u) => (u.id === savedUser.id ? savedUser : u)),
}
}
return {
...prev,
items: [savedUser, ...prev.items],
meta: { ...prev.meta, total: prev.meta.total + 1 },
}
})
}, [])
const handleUserDeleted = useCallback((id: number) => {
setData((prev) => {
const nextItems = prev.items.filter((u) => u.id !== id)
const removed = nextItems.length !== prev.items.length
return {
...prev,
items: nextItems,
meta: {
...prev.meta,
total: removed ? Math.max(0, prev.meta.total - 1) : prev.meta.total,
},
}
})
}, [])
const handleDelete = useCallback(async (user: User) => {
const result = await Swal.fire({
...getSwalThemeOptions(),
icon: 'warning',
title: 'Kullanıcı silinsin mi?',
text: `${user.username} kalıcı olarak silinecek.`,
showCancelButton: true,
confirmButtonText: 'Evet, sil',
cancelButtonText: 'İptal',
confirmButtonColor: '#dc2626',
reverseButtons: true,
focusCancel: true,
})
if (!result.isConfirmed) return
const formData = new FormData()
formData.set('id', String(user.id))
const deleted = await deleteUser({}, formData)
if (!deleted.success) {
showToast('error', deleted.error ?? 'Kullanıcı silinemedi')
return
}
handleUserDeleted(deleted.deletedId ?? user.id)
showToast('success', 'Kullanıcı silindi')
}, [handleUserDeleted])
async function handleStatusToggle(user: User) {
const nextStatus = !user.is_active
const statusLabel = nextStatus ? 'aktif' : 'pasif'
const decision = await Swal.fire({
...getSwalThemeOptions(),
icon: 'question',
title: 'Durum değiştirilsin mi?',
text: `${user.username} kullanıcısı ${statusLabel} yapılacak.`,
showCancelButton: true,
confirmButtonText: 'Evet',
cancelButtonText: 'İptal',
reverseButtons: true,
focusCancel: true,
})
if (!decision.isConfirmed) return
startTransition(async () => {
const result = await changeUserStatus(user.id, nextStatus)
if (!result.success || !result.user) {
showToast('error', result.error ?? 'Durum güncellenemedi')
return
}
setData((prev) => ({
...prev,
items: prev.items.map((u) => (u.id === result.user!.id ? result.user! : u)),
}))
showToast('success', `Kullanıcı ${statusLabel} yapıldı`)
})
}
return (
{/* Header */}
Kullanıcılar
Toplam {data.meta.total} kullanıcı
{/* Table */}
ID
Kullanıcı Adı
E-posta
Durum
Rol
Kayıt Tarihi
İşlemler
{data.items.map((user) => (
{user.id}
{user.username}
{user.email}
{user.is_admin
? Admin
: Kullanıcı
}
{new Date(user.created_at).toLocaleDateString('tr-TR')}
))}
{data.items.length === 0 && (
Henüz kullanıcı yok
)}
{/* Pagination */}
{data.meta.total > data.meta.limit && (
Sayfa {data.meta.page} / {Math.ceil(data.meta.total / data.meta.limit)}
)}
{/* Modals */}
)
}