410 lines
16 KiB
TypeScript
410 lines
16 KiB
TypeScript
"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>
|
||
)
|
||
}
|