Initial commit
This commit is contained in:
409
app/role-admin/page.tsx
Normal file
409
app/role-admin/page.tsx
Normal file
@@ -0,0 +1,409 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { ArrowRight, Users, UserCog, MessageSquare, KeyRound, Trash2, Home } from "lucide-react"
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||
import { RoleManagementModal } from "@/components/role-management-modal"
|
||||
import { ReportOnBehalfModal } from "@/components/report-on-behalf-modal"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { type User, type UserRole, ROLE_NAMES } from "@/types/user"
|
||||
|
||||
interface AdminUser {
|
||||
national_id: string
|
||||
name: string
|
||||
role: UserRole
|
||||
field?: string
|
||||
department?: string
|
||||
team?: string
|
||||
}
|
||||
|
||||
export default function RoleAdminPage() {
|
||||
const [admin, setAdmin] = useState<AdminUser | null>(null)
|
||||
const [users, setUsers] = useState<User[]>([])
|
||||
const [filteredUsers, setFilteredUsers] = useState<User[]>([])
|
||||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [message, setMessage] = useState("")
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [roleModalOpen, setRoleModalOpen] = useState(false)
|
||||
const [reportModalOpen, setReportModalOpen] = useState(false)
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
const userData = localStorage.getItem("user")
|
||||
if (!userData) {
|
||||
router.push("/login")
|
||||
return
|
||||
}
|
||||
|
||||
const parsedUser = JSON.parse(userData)
|
||||
if (parsedUser.role === "user") {
|
||||
router.push("/dashboard")
|
||||
return
|
||||
}
|
||||
|
||||
setAdmin(parsedUser)
|
||||
fetchManageableUsers(parsedUser.national_id)
|
||||
}, [router])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchTerm) {
|
||||
setFilteredUsers(users.filter((user) => user.name.includes(searchTerm)))
|
||||
} else {
|
||||
setFilteredUsers(users)
|
||||
}
|
||||
}, [searchTerm, users])
|
||||
|
||||
const fetchManageableUsers = async (adminId: string) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch("/api/admin/manageable-users", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ adminId }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (response.ok) {
|
||||
setUsers(data.users)
|
||||
} else {
|
||||
setMessage(data.error || "שגיאה בטעינת משתמשים")
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage("שגיאה בחיבור לשרת")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRoleChange = async (userId: string, newRole: string) => {
|
||||
try {
|
||||
const response = await fetch("/api/admin/change-role", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
adminId: admin?.national_id,
|
||||
targetUserId: userId,
|
||||
newRole,
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (response.ok) {
|
||||
setMessage(data.message)
|
||||
fetchManageableUsers(admin?.national_id || "")
|
||||
} else {
|
||||
setMessage(data.error || "שגיאה בשינוי תפקיד")
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage("שגיאה בחיבור לשרת")
|
||||
}
|
||||
}
|
||||
|
||||
const handleReportOnBehalf = async (userId: string, status: string) => {
|
||||
try {
|
||||
const response = await fetch("/api/admin/report-on-behalf", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
adminId: admin?.national_id,
|
||||
targetUserId: userId,
|
||||
status,
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
if (response.ok) {
|
||||
setMessage(data.message)
|
||||
fetchManageableUsers(admin?.national_id || "")
|
||||
} else {
|
||||
setMessage(data.error || "שגיאה בדיווח")
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage("שגיאה בחיבור לשרת")
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteUser = async (nationalId: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/admin/users/${nationalId}`, {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
adminId: admin?.national_id,
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
setMessage("משתמש נמחק בהצלחה")
|
||||
fetchManageableUsers(admin?.national_id || "")
|
||||
} else {
|
||||
const data = await response.json()
|
||||
setMessage(data.error || "שגיאה במחיקת משתמש")
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage("שגיאה בחיבור לשרת")
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetPassword = async (nationalId: string, userName: string) => {
|
||||
try {
|
||||
const response = await fetch("/api/admin/reset-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
adminId: admin?.national_id,
|
||||
targetUserId: nationalId,
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
setMessage(`סיסמה אופסה בהצלחה עבור ${userName}. הסיסמה החדשה: password123`)
|
||||
fetchManageableUsers(admin?.national_id || "")
|
||||
} else {
|
||||
const data = await response.json()
|
||||
setMessage(data.error || "שגיאה באיפוס סיסמה")
|
||||
}
|
||||
} catch (err) {
|
||||
setMessage("שגיאה בחיבור לשרת")
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status?: string) => {
|
||||
switch (status) {
|
||||
case "yes":
|
||||
return { text: "במקלט/חדר מוגן", color: "text-green-600" }
|
||||
case "no":
|
||||
return { text: "לא במקלט", color: "text-orange-600" }
|
||||
case "no_alarm":
|
||||
return { text: "אין אזעקה", color: "text-blue-600" }
|
||||
case "safe_after_exit":
|
||||
return { text: "אני בטוח (אחרי יציאה מהמקלט)", color: "text-purple-600" }
|
||||
default:
|
||||
return { text: "לא דיווח", color: "text-gray-500" }
|
||||
}
|
||||
}
|
||||
|
||||
const getScopeText = () => {
|
||||
if (!admin) return ""
|
||||
|
||||
switch (admin.role) {
|
||||
case "global_admin":
|
||||
return "כל המערכת"
|
||||
case "field_admin":
|
||||
return `תחום ${admin.field}`
|
||||
case "department_admin":
|
||||
return `מסגרת ${admin.department}`
|
||||
case "team_admin":
|
||||
return `צוות ${admin.team}`
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
if (!admin) return null
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-4" dir="rtl">
|
||||
<div className="max-w-6xl mx-auto space-y-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<CardTitle className="text-xl">ניהול תפקידים ודיווחים</CardTitle>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
{ROLE_NAMES[admin.role]} - {getScopeText()}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={() => router.push("/admin")} className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => router.push("/dashboard")} className="flex items-center gap-2">
|
||||
<Home className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
||||
{message && (
|
||||
<Alert>
|
||||
<AlertDescription>{message}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
ניהול משתמשים ({filteredUsers.length})
|
||||
</CardTitle>
|
||||
<div className="flex gap-4">
|
||||
<Input
|
||||
placeholder="חיפוש לפי שם..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="max-w-md"
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="text-center py-8">טוען משתמשים...</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="text-right">שם</TableHead>
|
||||
<TableHead className="text-right">מזהה</TableHead>
|
||||
<TableHead className="text-right">תפקיד</TableHead>
|
||||
<TableHead className="text-right">תחום</TableHead>
|
||||
<TableHead className="text-right">מסגרת</TableHead>
|
||||
<TableHead className="text-right">צוות</TableHead>
|
||||
<TableHead className="text-right">פעולות</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="text-right" dir="rtl">
|
||||
{filteredUsers.map((user) => {
|
||||
const status = getStatusText(user.in_shelter)
|
||||
return (
|
||||
<TableRow key={user.national_id}>
|
||||
<TableCell className="font-medium">{user.name}</TableCell>
|
||||
<TableCell className="font-mono text-right" dir="ltr">
|
||||
{user.national_id}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm bg-indigo-100 text-indigo-800 px-2 py-1 rounded">
|
||||
{ROLE_NAMES[user.role] || user.role}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm bg-green-100 text-green-800 px-2 py-1 rounded">
|
||||
{user.field || "לא הוגדר"}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
|
||||
{user.department || "לא הוגדר"}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm bg-purple-100 text-purple-800 px-2 py-1 rounded">
|
||||
{user.team || "לא הוגדר"}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="text-right" dir="rtl">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedUser(user)
|
||||
setRoleModalOpen(true)
|
||||
}}
|
||||
className="text-blue-600 hover:text-blue-700"
|
||||
>
|
||||
<UserCog className="h-4 w-4" />
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="text-red-600 hover:text-red-700">
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent dir="rtl">
|
||||
<AlertDialogHeader className="text-right" dir="rtl">
|
||||
<AlertDialogTitle className="text-right" dir="rtl">האם אתה בטוח?</AlertDialogTitle>
|
||||
<AlertDialogDescription className="text-right" dir="rtl">
|
||||
פעולה זו בלתי הפיכה. האם אתה בטוח שברצונך למחוק את {user.name}?
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter dir="rtl" className="flex-row-reverse sm:justify-start">
|
||||
<AlertDialogCancel>ביטול</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
if (user.national_id === admin?.national_id) {
|
||||
setMessage("לא ניתן למחוק את עצמך")
|
||||
return
|
||||
}
|
||||
handleDeleteUser(user.national_id)
|
||||
}}
|
||||
>
|
||||
מחיקה
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="text-orange-600 hover:text-orange-700">
|
||||
<KeyRound className="h-4 w-4" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent dir="rtl">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>איפוס סיסמה</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
האם אתה בטוח שברצונך לאפס את הסיסמה של {user.name}? הסיסמה החדשה תהיה: password123
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>ביטול</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => handleResetPassword(user.national_id, user.name)}>
|
||||
איפוס סיסמה
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{filteredUsers.length === 0 && (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{searchTerm ? "לא נמצאו משתמשים התואמים לחיפוש" : "אין משתמשים"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<RoleManagementModal
|
||||
isOpen={roleModalOpen}
|
||||
onClose={() => setRoleModalOpen(false)}
|
||||
user={selectedUser}
|
||||
adminRole={admin.role}
|
||||
onRoleChange={handleRoleChange}
|
||||
/>
|
||||
|
||||
<ReportOnBehalfModal
|
||||
isOpen={reportModalOpen}
|
||||
onClose={() => setReportModalOpen(false)}
|
||||
user={selectedUser}
|
||||
onReport={handleReportOnBehalf}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user