224 lines
9.1 KiB
TypeScript
224 lines
9.1 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { useAppDispatch, useAppSelector } from "@/lib/hooks";
|
||
import { fetchUsers, fetchDeletedUsers, createUser, updateUser, deleteUser, restoreUser, CreateUserRequest, UpdateUserRequest } from "@/lib/features/users/usersSlice";
|
||
import { UserTable } from "@/components/users/user-table";
|
||
import { DeletedUserTable } from "@/components/users/deleted-user-table";
|
||
import { UserDialog } from "@/components/users/user-dialog";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Plus, Trash, Trash2 } from "lucide-react";
|
||
import Swal from "sweetalert2";
|
||
import { User } from "@/lib/features/auth/authSlice";
|
||
|
||
export default function UsersPage() {
|
||
const dispatch = useAppDispatch();
|
||
const { users, deletedUsers, isLoading, error } = useAppSelector((state) => state.users);
|
||
const [dialogOpen, setDialogOpen] = useState(false);
|
||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||
|
||
useEffect(() => {
|
||
dispatch(fetchUsers());
|
||
dispatch(fetchDeletedUsers());
|
||
}, [dispatch]);
|
||
|
||
const handleCreate = () => {
|
||
setSelectedUser(null);
|
||
setDialogOpen(true);
|
||
};
|
||
|
||
const handleEdit = (user: User) => {
|
||
setSelectedUser(user);
|
||
setDialogOpen(true);
|
||
};
|
||
|
||
const handleDelete = (id: string) => {
|
||
Swal.fire({
|
||
title: "Soft Delete",
|
||
text: "Bu kullanıcı 'silindi' olarak işaretlenecek (Soft Delete). Geri alınabilir.",
|
||
icon: "warning",
|
||
showCancelButton: true,
|
||
confirmButtonColor: "#f97316", // Orange
|
||
cancelButtonColor: "#3085d6",
|
||
confirmButtonText: "Evet, Sil",
|
||
cancelButtonText: "İptal",
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
dispatch(deleteUser(id))
|
||
.unwrap()
|
||
.then(() => {
|
||
Swal.fire("Silindi!", "Kullanıcı başarıyla silindi (Soft).", "success");
|
||
// Refresh both lists because a user moved from active to deleted
|
||
dispatch(fetchUsers());
|
||
dispatch(fetchDeletedUsers());
|
||
})
|
||
.catch((err) => {
|
||
Swal.fire("Hata!", err || "Silme işlemi başarısız.", "error");
|
||
});
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleHardDelete = (id: string) => {
|
||
Swal.fire({
|
||
title: "KALICI OLARAK SİL?",
|
||
text: "Bu işlem GERİ ALINAMAZ! Kullanıcı ve tüm verileri veritabanından tamamen silinecek (Hard Delete).",
|
||
icon: "error", // Red
|
||
showCancelButton: true,
|
||
confirmButtonColor: "#d33", // Red
|
||
cancelButtonColor: "#3085d6",
|
||
confirmButtonText: "Evet, KALICI SİL!",
|
||
cancelButtonText: "İptal",
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
const isSoftDeleted = deletedUsers.some(u => u.id === id);
|
||
|
||
if (isSoftDeleted) {
|
||
// WORKAROUND: Backend soft-deleted kullanıcıları bulamıyor olabilir.
|
||
// Önce restore et, sonra hard delete yap.
|
||
dispatch(restoreUser(id))
|
||
.unwrap()
|
||
.then(() => {
|
||
return dispatch(deleteUser({ id, hard: true })).unwrap();
|
||
})
|
||
.then(() => {
|
||
Swal.fire("Silindi!", "Kullanıcı kalıcı olarak silindi.", "success");
|
||
})
|
||
.catch((err) => {
|
||
Swal.fire("Hata!", err || "Silme işlemi başarısız.", "error");
|
||
});
|
||
} else {
|
||
// Normal Hard Delete (Aktif kullanıcı)
|
||
dispatch(deleteUser({ id, hard: true }))
|
||
.unwrap()
|
||
.then(() => {
|
||
Swal.fire("Silindi!", "Kullanıcı kalıcı olarak silindi.", "success");
|
||
})
|
||
.catch((err) => {
|
||
Swal.fire("Hata!", err || "Silme işlemi başarısız.", "error");
|
||
});
|
||
}
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleRestore = (id: string) => {
|
||
Swal.fire({
|
||
title: "Geri Yükle?",
|
||
text: "Kullanıcı tekrar aktif edilecek.",
|
||
icon: "question",
|
||
showCancelButton: true,
|
||
confirmButtonColor: "#16a34a", // Green
|
||
cancelButtonColor: "#d33",
|
||
confirmButtonText: "Evet, Geri Yükle",
|
||
cancelButtonText: "İptal",
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
dispatch(restoreUser(id))
|
||
.unwrap()
|
||
.then(() => {
|
||
Swal.fire("Başarılı!", "Kullanıcı geri yüklendi.", "success");
|
||
dispatch(fetchUsers());
|
||
dispatch(fetchDeletedUsers());
|
||
})
|
||
.catch((err) => {
|
||
Swal.fire("Hata!", err || "Geri yükleme başarısız.", "error");
|
||
});
|
||
}
|
||
});
|
||
};
|
||
|
||
const handleSubmit = (data: CreateUserRequest | UpdateUserRequest) => {
|
||
if ("id" in data) {
|
||
dispatch(updateUser(data as UpdateUserRequest))
|
||
.unwrap()
|
||
.then(() => {
|
||
setDialogOpen(false);
|
||
Swal.fire("Başarılı", "Kullanıcı güncellendi.", "success");
|
||
dispatch(fetchUsers());
|
||
})
|
||
.catch((err) => {
|
||
Swal.fire("Hata", err || "Güncelleme başarısız.", "error");
|
||
});
|
||
} else {
|
||
dispatch(createUser(data as CreateUserRequest))
|
||
.unwrap()
|
||
.then(() => {
|
||
setDialogOpen(false);
|
||
Swal.fire("Başarılı", "Kullanıcı oluşturuldu.", "success");
|
||
dispatch(fetchUsers());
|
||
})
|
||
.catch((err) => {
|
||
Swal.fire("Hata", err || "Oluşturma başarısız.", "error");
|
||
});
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-12">
|
||
{/* Active Users Section */}
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h2 className="text-md font-bold tracking-tight">Kullanıcılar</h2>
|
||
<p className="text-muted-foreground">
|
||
Sistemdeki aktif kullanıcıları yönetin.
|
||
</p>
|
||
</div>
|
||
<Button onClick={() => { setSelectedUser(null); setDialogOpen(true); }}>
|
||
<Plus className="mr-2 h-4 w-4" /> Yeni Kullanıcı
|
||
</Button>
|
||
</div>
|
||
|
||
{error && (
|
||
<div className="rounded-md bg-destructive/15 p-4 text-destructive">
|
||
{error}
|
||
</div>
|
||
)}
|
||
|
||
<UserTable
|
||
users={users}
|
||
onEdit={(u) => { setSelectedUser(u); setDialogOpen(true); }}
|
||
onDelete={handleDelete}
|
||
onHardDelete={handleHardDelete}
|
||
/>
|
||
</div>
|
||
|
||
{/* Deleted Users Section */}
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h2 className="text-2xl font-bold tracking-tight text-destructive flex items-center gap-2">
|
||
<Trash2 className="h-6 w-6" /> Çöp Kutusu
|
||
</h2>
|
||
<p className="text-muted-foreground">
|
||
Silinmiş kullanıcıları geri yükleyin veya kalıcı olarak silin.
|
||
</p>
|
||
</div>
|
||
|
||
<DeletedUserTable
|
||
users={deletedUsers}
|
||
onRestore={handleRestore}
|
||
onHardDelete={handleHardDelete}
|
||
/>
|
||
</div>
|
||
|
||
<UserDialog
|
||
open={dialogOpen}
|
||
onOpenChange={setDialogOpen}
|
||
user={selectedUser}
|
||
onSubmit={(data) => {
|
||
// Re-implementing handleSubmit logic inline or keep it separate as before if preferred,
|
||
// but for brevity I'll assume the previous handleSubmit function is available in scope
|
||
// or I should include it in replacement if I am replacing the whole return block.
|
||
// Ideally, I should keep the existing handleSubmit and just pass it.
|
||
// Since I am replacing the whole return, I must ensure handleSubmit is defined above or passed correctly.
|
||
// Wait, I am replacing a range that includes imports and component setup? No, just the function body?
|
||
// Let's modify the instruction to be safer.
|
||
handleSubmit(data);
|
||
}}
|
||
isLoading={isLoading}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|