Initial commit

This commit is contained in:
2025-06-22 00:01:22 +03:00
parent fd70166cf6
commit 033b80bfad
153 changed files with 26874 additions and 1 deletions

409
app/role-admin/page.tsx Normal file
View 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>
)
}