Updated to using managed types instead of
hard coded ones.
This commit is contained in:
@@ -9,18 +9,7 @@ import { useRouter } from "next/navigation"
|
||||
// ... (other imports)
|
||||
|
||||
// Updated import to get all necessary types and constants from types/user.ts
|
||||
import {
|
||||
type User,
|
||||
type UserRole,
|
||||
type Field,
|
||||
type Department,
|
||||
type Team,
|
||||
ROLE_NAMES,
|
||||
SHELTER_STATUS_NAMES, // You might need this if displaying status names
|
||||
DEPARTMENTS, // If you use this array anywhere for dropdowns/validation
|
||||
TEAMS, // If you use this array anywhere for dropdowns/validation
|
||||
FIELDS, // If you use this array anywhere for dropdowns/validation
|
||||
} from "@/types/user"
|
||||
import { type User, type UserRole, ROLE_NAMES } from "@/types/user"
|
||||
|
||||
// ... (rest of your component code)
|
||||
|
||||
@@ -65,6 +54,7 @@ import {
|
||||
MessageSquare,
|
||||
Lock,
|
||||
LockOpen,
|
||||
Pencil,
|
||||
ArrowLeft,
|
||||
Home,
|
||||
} from "lucide-react"
|
||||
@@ -80,6 +70,7 @@ import { useDepartmentRealTimeUpdates } from "@/hooks/useDepartmentRealTimeUpdat
|
||||
import { FieldUserCategoryModal } from "@/components/field-user-category-modal"
|
||||
import { useFieldRealTimeUpdates } from "@/hooks/useFieldRealTimeUpdates"
|
||||
import { ReportOnBehalfModal } from "@/components/report-on-behalf-modal"
|
||||
import { UserScopeModal } from "@/components/user-scope-modal"
|
||||
|
||||
interface Stats {
|
||||
no_report: number
|
||||
@@ -102,6 +93,13 @@ interface UserData {
|
||||
lock_status?: boolean
|
||||
}
|
||||
|
||||
interface ManagedTypeOption {
|
||||
id?: number
|
||||
name: string
|
||||
managed: boolean
|
||||
parentId?: number | null
|
||||
}
|
||||
|
||||
export default function AdminPage() {
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [activeTab, setActiveTab] = useState("team")
|
||||
@@ -142,6 +140,25 @@ export default function AdminPage() {
|
||||
team: "",
|
||||
role: "",
|
||||
})
|
||||
const [managedTypes, setManagedTypes] = useState<{
|
||||
fields: ManagedTypeOption[]
|
||||
departments: ManagedTypeOption[]
|
||||
teams: ManagedTypeOption[]
|
||||
}>({
|
||||
fields: [],
|
||||
departments: [],
|
||||
teams: [],
|
||||
})
|
||||
const [managedTypesLoading, setManagedTypesLoading] = useState(false)
|
||||
const [managedTypeTab, setManagedTypeTab] = useState<"field" | "department" | "team">("field")
|
||||
const [newFieldName, setNewFieldName] = useState("")
|
||||
const [newDepartmentName, setNewDepartmentName] = useState("")
|
||||
const [newTeamName, setNewTeamName] = useState("")
|
||||
const [newDepartmentParentId, setNewDepartmentParentId] = useState<string>("")
|
||||
const [newTeamParentId, setNewTeamParentId] = useState<string>("")
|
||||
const [scopeModalOpen, setScopeModalOpen] = useState(false)
|
||||
const [scopeUser, setScopeUser] = useState<UserData | null>(null)
|
||||
const [scopeSaving, setScopeSaving] = useState(false)
|
||||
const [message, setMessage] = useState("")
|
||||
const [loadingUsers, setLoadingUsers] = useState(false)
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
@@ -316,6 +333,43 @@ export default function AdminPage() {
|
||||
setUser(parsedUser)
|
||||
}, [router])
|
||||
|
||||
const fetchManagedTypes = async () => {
|
||||
setManagedTypesLoading(true)
|
||||
try {
|
||||
const response = await fetch(`/api/admin/managed-types?adminId=${encodeURIComponent(user?.national_id || "")}`)
|
||||
const data = await response.json()
|
||||
if (response.ok) {
|
||||
setManagedTypes({
|
||||
fields: data.fields || [],
|
||||
departments: data.departments || [],
|
||||
teams: data.teams || [],
|
||||
})
|
||||
} else {
|
||||
setMessage(data.error || "Failed to load managed types.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Managed types fetch error:", error)
|
||||
setMessage("Failed to load managed types.")
|
||||
} finally {
|
||||
setManagedTypesLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.national_id) {
|
||||
fetchManagedTypes()
|
||||
}
|
||||
}, [user?.national_id])
|
||||
|
||||
useEffect(() => {
|
||||
if (!newDepartmentParentId && managedTypes.fields.length === 1 && managedTypes.fields[0].id) {
|
||||
setNewDepartmentParentId(String(managedTypes.fields[0].id))
|
||||
}
|
||||
if (!newTeamParentId && managedTypes.departments.length === 1 && managedTypes.departments[0].id) {
|
||||
setNewTeamParentId(String(managedTypes.departments[0].id))
|
||||
}
|
||||
}, [managedTypes, newDepartmentParentId, newTeamParentId])
|
||||
|
||||
useEffect(() => {
|
||||
if (globalResetCooldown > 0) {
|
||||
const timer = setTimeout(() => setGlobalResetCooldown(globalResetCooldown - 1), 1000)
|
||||
@@ -474,6 +528,108 @@ export default function AdminPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddManagedType = async (type: "field" | "department" | "team") => {
|
||||
const name =
|
||||
type === "field" ? newFieldName : type === "department" ? newDepartmentName : newTeamName
|
||||
|
||||
if (!name.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
const parentId =
|
||||
type === "department"
|
||||
? newDepartmentParentId
|
||||
: type === "team"
|
||||
? newTeamParentId
|
||||
: ""
|
||||
|
||||
if ((type === "department" || type === "team") && !parentId) {
|
||||
setMessage("Select a parent before adding.")
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/admin/managed-types", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
adminId: user?.national_id,
|
||||
type,
|
||||
name: name.trim(),
|
||||
parentId: parentId ? Number(parentId) : undefined,
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
if (type === "field") setNewFieldName("")
|
||||
if (type === "department") setNewDepartmentName("")
|
||||
if (type === "team") setNewTeamName("")
|
||||
if (type === "department") setNewDepartmentParentId("")
|
||||
if (type === "team") setNewTeamParentId("")
|
||||
await fetchManagedTypes()
|
||||
} else {
|
||||
setMessage(data.error || "שגיאה בהוספת Type מנוהל")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Managed type add error:", error)
|
||||
setMessage("שגיאה בהוספת Type מנוהל")
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteManagedType = async (id?: number) => {
|
||||
if (!id || !user?.national_id) return
|
||||
if (!confirm("למחוק ערך זה? יש לשייך לפני המחיקה את כלל המשתמשים תחת ערך זה מחדש.")) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/managed-types/${id}`, {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ adminId: user.national_id }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
await fetchManagedTypes()
|
||||
} else {
|
||||
setMessage(data.error || "Failed to delete managed type.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Managed type delete error:", error)
|
||||
setMessage("Failed to delete managed type.")
|
||||
}
|
||||
}
|
||||
|
||||
const handleRenameManagedType = async (id?: number, currentName?: string) => {
|
||||
if (!id || !user?.national_id || !currentName) return
|
||||
const nextName = prompt("Rename value:", currentName)
|
||||
if (!nextName || nextName.trim() === currentName) return
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/admin/managed-types/${id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ adminId: user.national_id, name: nextName.trim() }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
await fetchManagedTypes()
|
||||
await Promise.all([refetchGlobal(), refetchTeam(), refetchDepartment(), refetchField()])
|
||||
} else {
|
||||
setMessage(data.error || "Failed to rename managed type.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Managed type rename error:", error)
|
||||
setMessage("Failed to rename managed type.")
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddUser = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -597,6 +753,45 @@ export default function AdminPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleUpdateUserScope = async (payload: {
|
||||
userId: string
|
||||
field: string
|
||||
department: string
|
||||
team: string
|
||||
}) => {
|
||||
if (!user?.national_id) return
|
||||
setScopeSaving(true)
|
||||
try {
|
||||
const response = await fetch("/api/admin/update-user-scope", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
adminId: user.national_id,
|
||||
targetUserId: payload.userId,
|
||||
field: payload.field,
|
||||
department: payload.department,
|
||||
team: payload.team,
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
setMessage(data.message || "User updated.")
|
||||
setScopeModalOpen(false)
|
||||
setScopeUser(null)
|
||||
await Promise.all([refetchGlobal(), refetchTeam(), refetchDepartment(), refetchField()])
|
||||
} else {
|
||||
setMessage(data.error || "Failed to update user.")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("User scope update error:", error)
|
||||
setMessage("Failed to update user.")
|
||||
} finally {
|
||||
setScopeSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status?: string) => {
|
||||
switch (status) {
|
||||
case "yes":
|
||||
@@ -833,6 +1028,20 @@ export default function AdminPage() {
|
||||
{!isReadOnly && (
|
||||
<TableCell>
|
||||
<div className="flex gap-2">
|
||||
{user?.role !== "user" && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setScopeUser(userData)
|
||||
setScopeModalOpen(true)
|
||||
}}
|
||||
className="text-blue-600 hover:text-blue-700"
|
||||
title="Edit assignment"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -889,6 +1098,21 @@ export default function AdminPage() {
|
||||
)
|
||||
}
|
||||
|
||||
const fieldNameById = new Map(managedTypes.fields.map((field) => [field.id, field.name]))
|
||||
const departmentNameById = new Map(managedTypes.departments.map((dept) => [dept.id, dept.name]))
|
||||
const allowedManagedTabs =
|
||||
user?.role === "global_admin" || user?.role === "field_admin"
|
||||
? ["field", "department", "team"]
|
||||
: user?.role === "department_admin"
|
||||
? ["department", "team"]
|
||||
: []
|
||||
|
||||
useEffect(() => {
|
||||
if (allowedManagedTabs.length > 0 && !allowedManagedTabs.includes(managedTypeTab)) {
|
||||
setManagedTypeTab(allowedManagedTabs[0] as "field" | "department" | "team")
|
||||
}
|
||||
}, [allowedManagedTabs, managedTypeTab])
|
||||
|
||||
if (!user) return null
|
||||
|
||||
return (
|
||||
@@ -1232,9 +1456,9 @@ export default function AdminPage() {
|
||||
<SelectValue placeholder="בחר תחום" />
|
||||
</SelectTrigger>
|
||||
<SelectContent dir="rtl">
|
||||
{FIELDS.map((field) => (
|
||||
<SelectItem key={field} value={field}>
|
||||
{field}
|
||||
{managedTypes.fields.map((field) => (
|
||||
<SelectItem key={field.name} value={field.name}>
|
||||
{field.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -1250,9 +1474,9 @@ export default function AdminPage() {
|
||||
<SelectValue placeholder="בחר מסגרת" />
|
||||
</SelectTrigger>
|
||||
<SelectContent dir="rtl">
|
||||
{DEPARTMENTS.map((dept) => (
|
||||
<SelectItem key={dept} value={dept}>
|
||||
{dept}
|
||||
{managedTypes.departments.map((dept) => (
|
||||
<SelectItem key={dept.name} value={dept.name}>
|
||||
{dept.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -1265,9 +1489,9 @@ export default function AdminPage() {
|
||||
<SelectValue placeholder="בחר צוות" />
|
||||
</SelectTrigger>
|
||||
<SelectContent dir="rtl">
|
||||
{TEAMS.map((team) => (
|
||||
<SelectItem key={team} value={team}>
|
||||
{team}
|
||||
{managedTypes.teams.map((team) => (
|
||||
<SelectItem key={team.name} value={team.name}>
|
||||
{team.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -1307,6 +1531,233 @@ export default function AdminPage() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{allowedManagedTabs.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Users className="h-5 w-5" />
|
||||
ניהול תחומים, מסגרות וצוותים
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs
|
||||
value={managedTypeTab}
|
||||
onValueChange={(value) => setManagedTypeTab(value as "field" | "department" | "team")}
|
||||
className="w-full"
|
||||
dir="rtl"
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
{allowedManagedTabs.includes("field") && <TabsTrigger value="field">תחומים</TabsTrigger>}
|
||||
{allowedManagedTabs.includes("department") && (
|
||||
<TabsTrigger value="department">מסגרות</TabsTrigger>
|
||||
)}
|
||||
{allowedManagedTabs.includes("team") && <TabsTrigger value="team">צוותים</TabsTrigger>}
|
||||
</TabsList>
|
||||
|
||||
{allowedManagedTabs.includes("field") && (
|
||||
<TabsContent value="field" className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={newFieldName}
|
||||
onChange={(e) => setNewFieldName(e.target.value)}
|
||||
placeholder="תחום חדש"
|
||||
disabled={managedTypesLoading}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleAddManagedType("field")}
|
||||
disabled={managedTypesLoading || !newFieldName.trim()}
|
||||
>
|
||||
הוספה
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
יש לשייך משתמשים לתחום אחר לפני מחיקה!
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{managedTypes.fields.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No fields yet.</div>
|
||||
) : (
|
||||
managedTypes.fields.map((item) => (
|
||||
<div key={item.name} className="flex items-center justify-between rounded border p-2">
|
||||
<div className="flex flex-col">
|
||||
<span>{item.name}</span>
|
||||
</div>
|
||||
{item.managed ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRenameManagedType(item.id, item.name)}
|
||||
>
|
||||
<Pencil className="h-4 w-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteManagedType(item.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-red-600" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-gray-400">In use</span>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{allowedManagedTabs.includes("department") && (
|
||||
<TabsContent value="department" className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Select value={newDepartmentParentId} onValueChange={setNewDepartmentParentId}>
|
||||
<SelectTrigger dir="rtl">
|
||||
<SelectValue placeholder="בחרו תחום" />
|
||||
</SelectTrigger>
|
||||
<SelectContent dir="rtl">
|
||||
{managedTypes.fields.filter((field) => field.id).map((field) => (
|
||||
<SelectItem key={field.name} value={String(field.id)}>
|
||||
{field.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={newDepartmentName}
|
||||
onChange={(e) => setNewDepartmentName(e.target.value)}
|
||||
placeholder="מסגרת חדשה"
|
||||
disabled={managedTypesLoading}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleAddManagedType("department")}
|
||||
disabled={managedTypesLoading || !newDepartmentName.trim()}
|
||||
>
|
||||
הוספה
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
יש לשייך משתמשים למסגרת אחרת לפני מחיקה!
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{managedTypes.departments.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No departments yet.</div>
|
||||
) : (
|
||||
managedTypes.departments.map((item) => (
|
||||
<div key={item.name} className="flex items-center justify-between rounded border p-2">
|
||||
<div className="flex flex-col">
|
||||
<span>{item.name}</span>
|
||||
{item.parentId && (
|
||||
<span className="text-xs text-gray-500">
|
||||
תחום: {fieldNameById.get(item.parentId) || "לא ידוע"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.managed ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRenameManagedType(item.id, item.name)}
|
||||
>
|
||||
<Pencil className="h-4 w-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteManagedType(item.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-red-600" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-gray-400">In use</span>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{allowedManagedTabs.includes("team") && (
|
||||
<TabsContent value="team" className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Select value={newTeamParentId} onValueChange={setNewTeamParentId}>
|
||||
<SelectTrigger dir="rtl">
|
||||
<SelectValue placeholder="בחרו מסגרת" />
|
||||
</SelectTrigger>
|
||||
<SelectContent dir="rtl">
|
||||
{managedTypes.departments.filter((dept) => dept.id).map((dept) => (
|
||||
<SelectItem key={dept.name} value={String(dept.id)}>
|
||||
{dept.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
value={newTeamName}
|
||||
onChange={(e) => setNewTeamName(e.target.value)}
|
||||
placeholder="צוות חדש"
|
||||
disabled={managedTypesLoading}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => handleAddManagedType("team")}
|
||||
disabled={managedTypesLoading || !newTeamName.trim()}
|
||||
>
|
||||
הוספה
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
יש לשייך משתמשים לצוות אחר לפני מחיקה!
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{managedTypes.teams.length === 0 ? (
|
||||
<div className="text-sm text-gray-500">No teams yet.</div>
|
||||
) : (
|
||||
managedTypes.teams.map((item) => (
|
||||
<div key={item.name} className="flex items-center justify-between rounded border p-2">
|
||||
<div className="flex flex-col">
|
||||
<span>{item.name}</span>
|
||||
{item.parentId && (
|
||||
<span className="text-xs text-gray-500">
|
||||
מסגרת: {departmentNameById.get(item.parentId) || "לא ידועה"}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{item.managed ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleRenameManagedType(item.id, item.name)}
|
||||
>
|
||||
<Pencil className="h-4 w-4 text-blue-600" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteManagedType(item.id)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-red-600" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-xs text-gray-400">In use</span>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</TabsContent>
|
||||
)}
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
@@ -1364,6 +1815,17 @@ export default function AdminPage() {
|
||||
fieldName={fieldName}
|
||||
/>
|
||||
|
||||
<UserScopeModal
|
||||
isOpen={scopeModalOpen}
|
||||
onClose={() => setScopeModalOpen(false)}
|
||||
user={scopeUser}
|
||||
fields={managedTypes.fields}
|
||||
departments={managedTypes.departments}
|
||||
teams={managedTypes.teams}
|
||||
onSave={handleUpdateUserScope}
|
||||
isSaving={scopeSaving}
|
||||
/>
|
||||
|
||||
<ReportOnBehalfModal
|
||||
isOpen={reportModalOpen}
|
||||
onClose={() => setReportModalOpen(false)}
|
||||
|
||||
Reference in New Issue
Block a user