1.1.4 - updated user modals #6

Merged
tomchukk merged 1 commits from 1.1 into main 2026-01-16 23:46:20 +02:00
16 changed files with 288 additions and 109 deletions
Showing only changes of commit 458a78795d - Show all commits

View File

@@ -83,6 +83,7 @@ interface Stats {
interface UserData { interface UserData {
national_id: string national_id: string
name: string name: string
role?: string
in_shelter?: string in_shelter?: string
last_updated?: string last_updated?: string
is_admin: boolean is_admin: boolean
@@ -650,35 +651,64 @@ export default function AdminPage() {
const handleAddUser = async (e: React.FormEvent) => { const handleAddUser = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
if (!newUser.field || !newUser.department || !newUser.team || !newUser.role) { const trimmedField = newUser.field.trim()
setMessage("יש לבחור תפקיד, תחום, מסגרת וצוות") const trimmedDepartment = newUser.department.trim()
const trimmedTeam = newUser.team.trim()
const selectedRole = newUser.role as UserRole
if (!trimmedField || !selectedRole) {
setMessage("Please fill in the required fields.")
return return
} }
if (selectedRole === "department_admin") {
if (!trimmedDepartment) {
setMessage("Please fill in the required fields.")
return
}
} else if (selectedRole === "team_admin" || selectedRole === "user") {
if (!trimmedDepartment || !trimmedTeam) {
setMessage("Please fill in the required fields.")
return
}
}
try { try {
const payload = {
...newUser,
field: trimmedField,
department: trimmedDepartment,
team: trimmedTeam,
adminId: user?.national_id,
}
if (selectedRole === "field_admin") {
payload.department = ""
payload.team = ""
} else if (selectedRole === "department_admin") {
payload.team = ""
}
const response = await fetch("/api/admin/add-user", { const response = await fetch("/api/admin/add-user", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify(payload),
...newUser,
adminId: user?.national_id,
}),
}) })
const data = await response.json() const data = await response.json()
if (response.ok) { if (response.ok) {
setMessage(`${data.message}. הסיסמה הזמנית: password123`) setMessage(`${data.message}. Default password: password123`)
setNewUser({ name: "", isAdmin: false, field: "", department: "", team: "", role: "" }) setNewUser({ name: "", isAdmin: false, field: "", department: "", team: "", role: "" })
refetchGlobal() refetchGlobal()
refetchTeam() refetchTeam()
refetchDepartment() refetchDepartment()
refetchField() refetchField()
} else { } else {
setMessage(data.error || "שגיאה בהוספת משתמש") setMessage(data.error || "Failed to add user.")
} }
} catch (err) { } catch (err) {
setMessage("שגיאה בחיבור לשרת") setMessage("Failed to add user.")
} }
} }
@@ -1130,6 +1160,34 @@ export default function AdminPage() {
} }
}, [allowedManagedTabs, managedTypeTab]) }, [allowedManagedTabs, managedTypeTab])
const canShowTeamTab =
(user?.role === "team_admin" && !!user?.team) || (user?.role === "global_admin" && !!user?.team)
const canShowDepartmentTab =
(user?.role === "department_admin" && !!user?.department) ||
(user?.role === "global_admin" && !!user?.department)
const canShowFieldTab =
user?.role === "field_admin" || (user?.role === "global_admin" && !!user?.field)
const visibleTabs = [
canShowTeamTab ? "team" : null,
canShowDepartmentTab ? "department" : null,
canShowFieldTab ? "field" : null,
"global",
].filter(Boolean) as string[]
const tabColumns =
visibleTabs.length === 1
? "grid-cols-1"
: visibleTabs.length === 2
? "grid-cols-2"
: visibleTabs.length === 3
? "grid-cols-3"
: "grid-cols-4"
useEffect(() => {
if (visibleTabs.length > 0 && !visibleTabs.includes(activeTab)) {
setActiveTab(visibleTabs[0])
}
}, [activeTab, visibleTabs])
if (!user) return null if (!user) return null
return ( return (
@@ -1204,25 +1262,32 @@ export default function AdminPage() {
)} )}
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" dir="rtl"> <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full" dir="rtl">
<TabsList className="grid w-full grid-cols-4"> <TabsList className={`grid w-full ${tabColumns}`}>
{canShowTeamTab && (
<TabsTrigger value="team" className="flex items-center gap-2"> <TabsTrigger value="team" className="flex items-center gap-2">
<UsersIcon className="h-4 w-4" /> <UsersIcon className="h-4 w-4" />
צוות צוות
</TabsTrigger> </TabsTrigger>
)}
{canShowDepartmentTab && (
<TabsTrigger value="department" className="flex items-center gap-2"> <TabsTrigger value="department" className="flex items-center gap-2">
<Building2 className="h-4 w-4" /> <Building2 className="h-4 w-4" />
מסגרת מסגרת
</TabsTrigger> </TabsTrigger>
)}
{canShowFieldTab && (
<TabsTrigger value="field" className="flex items-center gap-2"> <TabsTrigger value="field" className="flex items-center gap-2">
<Globe className="h-4 w-4" /> <Globe className="h-4 w-4" />
תחום תחום
</TabsTrigger> </TabsTrigger>
)}
<TabsTrigger value="global" className="flex items-center gap-2"> <TabsTrigger value="global" className="flex items-center gap-2">
<Globe className="h-4 w-4" /> <Globe className="h-4 w-4" />
כללי כללי
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
{canShowTeamTab && (
<TabsContent value="team" className="space-y-6"> <TabsContent value="team" className="space-y-6">
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<Card> <Card>
@@ -1278,7 +1343,9 @@ export default function AdminPage() {
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
)}
{canShowDepartmentTab && (
<TabsContent value="department" className="space-y-6"> <TabsContent value="department" className="space-y-6">
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<Card> <Card>
@@ -1343,7 +1410,9 @@ export default function AdminPage() {
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
)}
{canShowFieldTab && (
<TabsContent value="field" className="space-y-6"> <TabsContent value="field" className="space-y-6">
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<Card> <Card>
@@ -1408,6 +1477,7 @@ export default function AdminPage() {
</CardContent> </CardContent>
</Card> </Card>
</TabsContent> </TabsContent>
)}
<TabsContent value="global" className="space-y-6"> <TabsContent value="global" className="space-y-6">
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
@@ -1481,6 +1551,8 @@ export default function AdminPage() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
{newUser.role !== "field_admin" && (
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="department">מסגרת</Label> <Label htmlFor="department">מסגרת</Label>
<Select <Select
@@ -1499,6 +1571,9 @@ export default function AdminPage() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
)}
{newUser.role !== "field_admin" && newUser.role !== "department_admin" && (
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="team">צוות</Label> <Label htmlFor="team">צוות</Label>
<Select value={newUser.team} onValueChange={(value) => setNewUser({ ...newUser, team: value })}> <Select value={newUser.team} onValueChange={(value) => setNewUser({ ...newUser, team: value })}>
@@ -1514,15 +1589,21 @@ export default function AdminPage() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
)}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="role">הרשאה</Label> <Label htmlFor="role">הרשאה</Label>
<Select <Select
value={newUser.role} value={newUser.role}
onValueChange={(value) => onValueChange={(value) =>
setNewUser({ setNewUser((prev) => {
...newUser, const next = { ...prev, role: value, isAdmin: value !== "user" }
role: value, if (value === "field_admin") {
isAdmin: value !== "user", // Automatically set isAdmin next.department = ""
next.team = ""
} else if (value === "department_admin") {
next.team = ""
}
return next
}) })
} }
> >

View File

@@ -21,44 +21,52 @@ export async function POST(request: NextRequest) {
const { name, isAdmin, field, department, team, role } = await request.json() const { name, isAdmin, field, department, team, role } = await request.json()
// Input validation // Input validation
if (!name || !field || !department || !team) { if (!name || !field) {
return NextResponse.json({ error: "נתונים חסרים" }, { status: 400 }) return NextResponse.json({ error: "Invalid name or field." }, { status: 400 })
} }
// Validate department, team, and field against managed types // Validate field against managed types
if (!(await hasManagedType("field", field))) { if (!(await hasManagedType("field", field))) {
return NextResponse.json({ error: "Invalid field." }, { status: 400 }) return NextResponse.json({ error: "Invalid field." }, { status: 400 })
} }
if (!(await hasManagedType("department", department))) { const normalizedDepartment = typeof department === "string" && department.trim() ? department.trim() : null
if (normalizedDepartment && !(await hasManagedType("department", normalizedDepartment))) {
return NextResponse.json({ error: "Invalid department." }, { status: 400 }) return NextResponse.json({ error: "Invalid department." }, { status: 400 })
} }
if (!(await hasManagedType("team", team))) { const normalizedTeam = typeof team === "string" && team.trim() ? team.trim() : null
if (normalizedTeam && !(await hasManagedType("team", normalizedTeam))) {
return NextResponse.json({ error: "Invalid team." }, { status: 400 }) return NextResponse.json({ error: "Invalid team." }, { status: 400 })
} }
const fieldRows = (await safeQuery("SELECT id FROM managed_types WHERE type = 'field' AND name = ?", [ const fieldRows = (await safeQuery("SELECT id FROM managed_types WHERE type = 'field' AND name = ?", [
field, field,
])) as Array<{ id: number }> ])) as Array<{ id: number }>
const departmentRows = (await safeQuery( const departmentRows = normalizedDepartment
? ((await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?", "SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?",
[department], [normalizedDepartment],
)) as Array<{ id: number; parentId: number | null }> )) as Array<{ id: number; parentId: number | null }>)
const teamRows = (await safeQuery( : []
const teamRows = normalizedTeam
? ((await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'team' AND name = ?", "SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'team' AND name = ?",
[team], [normalizedTeam],
)) as Array<{ id: number; parentId: number | null }> )) as Array<{ id: number; parentId: number | null }>)
: []
if (fieldRows.length === 0 || departmentRows.length === 0 || teamRows.length === 0) { if (fieldRows.length === 0 || (normalizedDepartment && departmentRows.length === 0) || (normalizedTeam && teamRows.length === 0)) {
return NextResponse.json({ error: "Invalid field, department, or team." }, { status: 400 }) return NextResponse.json({ error: "Invalid field, department, or team." }, { status: 400 })
} }
if (departmentRows[0].parentId !== fieldRows[0].id) { if (normalizedDepartment && departmentRows[0].parentId !== fieldRows[0].id) {
return NextResponse.json({ error: "Department does not belong to field." }, { status: 400 }) return NextResponse.json({ error: "Department does not belong to field." }, { status: 400 })
} }
if (teamRows[0].parentId !== departmentRows[0].id) { if (normalizedTeam && normalizedDepartment && teamRows[0].parentId !== departmentRows[0].id) {
return NextResponse.json({ error: "Team does not belong to department." }, { status: 400 }) return NextResponse.json({ error: "Team does not belong to department." }, { status: 400 })
} }
@@ -68,7 +76,21 @@ export async function POST(request: NextRequest) {
const userRole: UserRole = (role as UserRole) || (isAdmin ? "global_admin" : "user") const userRole: UserRole = (role as UserRole) || (isAdmin ? "global_admin" : "user")
if (!validRoles.includes(userRole)) { if (!validRoles.includes(userRole)) {
return NextResponse.json({ error: "תפקיד לא תקין" }, { status: 400 }) return NextResponse.json({ error: "Invalid role." }, { status: 400 })
}
if (userRole === "field_admin") {
if (!field || normalizedDepartment || normalizedTeam) {
return NextResponse.json({ error: "Field admins must have a field only." }, { status: 400 })
}
} else if (userRole === "department_admin") {
if (!field || !normalizedDepartment || normalizedTeam) {
return NextResponse.json({ error: "Department admins must have field + department only." }, { status: 400 })
}
} else if (userRole === "team_admin" || userRole === "user") {
if (!field || !normalizedDepartment || !normalizedTeam) {
return NextResponse.json({ error: "Team/users require field, department, and team." }, { status: 400 })
}
} }
// Generate unique Login ID // Generate unique Login ID
@@ -80,7 +102,7 @@ export async function POST(request: NextRequest) {
await safeQuery( await safeQuery(
"INSERT INTO users (national_id, password, name, is_admin, role, must_change_password, field, department, team) VALUES (?, ?, ?, ?, ?, TRUE, ?, ?, ?)", "INSERT INTO users (national_id, password, name, is_admin, role, must_change_password, field, department, team) VALUES (?, ?, ?, ?, ?, TRUE, ?, ?, ?)",
[nationalId, hashedPassword, name, isAdmin, userRole, field, department, team], [nationalId, hashedPassword, name, isAdmin, userRole, field, normalizedDepartment, normalizedTeam],
) )
return NextResponse.json({ return NextResponse.json({

View File

@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
// Get admin's field and department // Get admin's field and department
const adminData = (await safeQuery( const adminData = (await safeQuery(
"SELECT field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -19,10 +19,14 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment } = adminData[0]
if (adminRole !== "department_admin" && adminRole !== "global_admin") {
return NextResponse.json({ error: "Insufficient permissions." }, { status: 403 })
}
if (!adminField || !adminDepartment) { if (!adminField || !adminDepartment) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 }) return NextResponse.json({ error: "Department is not assigned." }, { status: 400 })
} }
// Check cooldown for department resets // Check cooldown for department resets

View File

@@ -11,7 +11,7 @@ export async function POST(request: Request) {
// Get admin's field and department // Get admin's field and department
const adminData = (await executeQuery( const adminData = (await executeQuery(
"SELECT field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -19,10 +19,18 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment } = adminData[0]
if (!adminField || !adminDepartment) { if (adminRole !== "department_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 }) return NextResponse.json({
no_report: 0,
in_shelter: 0,
not_in_shelter: 0,
no_alarm: 0,
safe_after_exit: 0,
field: adminField,
department: adminDepartment,
})
} }
// Get department stats with field and department context // Get department stats with field and department context

View File

@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
// Get admin's field and department // Get admin's field and department
const adminData = (await executeQuery( const adminData = (await executeQuery(
"SELECT field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -19,10 +19,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment } = adminData[0]
if (!adminField || !adminDepartment) { if (adminRole !== "department_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 }) return NextResponse.json([])
} }
let query = "" let query = ""

View File

@@ -11,7 +11,7 @@ export async function POST(request: Request) {
// Get admin's field and department // Get admin's field and department
const adminData = (await executeQuery( const adminData = (await executeQuery(
"SELECT field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -19,10 +19,10 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment } = adminData[0]
if (!adminField || !adminDepartment) { if (adminRole !== "department_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 }) return NextResponse.json({ users: [], field: adminField, department: adminDepartment })
} }
// Get department users with field and department context // Get department users with field and department context
@@ -31,6 +31,7 @@ export async function POST(request: Request) {
SELECT SELECT
national_id, national_id,
name, name,
role,
in_shelter, in_shelter,
last_updated, last_updated,
is_admin, is_admin,

View File

@@ -30,6 +30,7 @@ export async function POST(request: Request) {
SELECT SELECT
national_id, national_id,
name, name,
role,
in_shelter, in_shelter,
last_updated, last_updated,
is_admin, is_admin,

View File

@@ -12,7 +12,7 @@ export async function POST(request: NextRequest) {
// Get admin's field, department, and team // Get admin's field, department, and team
const adminData = (await safeQuery( const adminData = (await safeQuery(
"SELECT field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -20,10 +20,14 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment, team: adminTeam } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment, team: adminTeam } = adminData[0]
if (adminRole !== "team_admin" && adminRole !== "global_admin") {
return NextResponse.json({ error: "Insufficient permissions." }, { status: 403 })
}
if (!adminField || !adminDepartment || !adminTeam) { if (!adminField || !adminDepartment || !adminTeam) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 }) return NextResponse.json({ error: "Team is not assigned." }, { status: 400 })
} }
await safeQuery( await safeQuery(

View File

@@ -11,7 +11,7 @@ export async function POST(request: Request) {
// Get admin's field, department, and team // Get admin's field, department, and team
const adminData = (await executeQuery( const adminData = (await executeQuery(
"SELECT field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -19,10 +19,19 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment, team: adminTeam } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment, team: adminTeam } = adminData[0]
if (!adminField || !adminDepartment || !adminTeam) { if (adminRole !== "team_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment || !adminTeam) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 }) return NextResponse.json({
no_report: 0,
in_shelter: 0,
not_in_shelter: 0,
no_alarm: 0,
safe_after_exit: 0,
field: adminField,
department: adminDepartment,
team: adminTeam,
})
} }
// Get team stats with full context (field + department + team) // Get team stats with full context (field + department + team)

View File

@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
// Get admin's field, department, and team // Get admin's field, department, and team
const adminData = (await executeQuery( const adminData = (await executeQuery(
"SELECT field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", "SELECT role, field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId], [adminId],
)) as any[] )) as any[]
@@ -19,10 +19,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment, team: adminTeam } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment, team: adminTeam } = adminData[0]
if (!adminField || !adminDepartment || !adminTeam) { if (adminRole !== "team_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment || !adminTeam) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 }) return NextResponse.json([])
} }
let query = "" let query = ""

View File

@@ -10,7 +10,7 @@ export async function POST(request: Request) {
} }
// Get admin's field, department, and team // Get admin's field, department, and team
const adminData = (await executeQuery("SELECT field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", [ const adminData = (await executeQuery("SELECT role, field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'", [
adminId, adminId,
])) as any[] ])) as any[]
@@ -18,10 +18,10 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 }) return NextResponse.json({ error: "מנהל לא נמצא" }, { status: 404 })
} }
const { field: adminField, department: adminDepartment, team: adminTeam } = adminData[0] const { role: adminRole, field: adminField, department: adminDepartment, team: adminTeam } = adminData[0]
if (!adminField || !adminDepartment || !adminTeam) { if (adminRole !== "team_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment || !adminTeam) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 }) return NextResponse.json({ users: [], field: adminField, department: adminDepartment, team: adminTeam })
} }
// Get team users with full context (field + department + team) // Get team users with full context (field + department + team)
@@ -30,6 +30,7 @@ export async function POST(request: Request) {
SELECT SELECT
national_id, national_id,
name, name,
role,
in_shelter, in_shelter,
last_updated, last_updated,
is_admin, is_admin,

View File

@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
try { try {
const { adminId, targetUserId, field, department, team } = await request.json() const { adminId, targetUserId, field, department, team } = await request.json()
if (!adminId || !targetUserId || !field || !department || !team) { if (!adminId || !targetUserId || !field ) {
return NextResponse.json({ error: "חסרים שדות חובה." }, { status: 400 }) return NextResponse.json({ error: "חסרים שדות חובה." }, { status: 400 })
} }
@@ -31,7 +31,7 @@ export async function POST(request: NextRequest) {
const admin = adminRows[0] const admin = adminRows[0]
const userRows = (await safeQuery("SELECT national_id, field, department, team FROM users WHERE national_id = ?", [ const userRows = (await safeQuery("SELECT national_id, role, field, department, team FROM users WHERE national_id = ?", [
targetUserId, targetUserId,
])) as any[] ])) as any[]
if (userRows.length === 0) { if (userRows.length === 0) {
@@ -39,10 +39,25 @@ export async function POST(request: NextRequest) {
} }
const targetUser = userRows[0] const targetUser = userRows[0]
const normalizedDepartment = typeof department === "string" && department.trim() ? department.trim() : null
const normalizedTeam = typeof team === "string" && team.trim() ? team.trim() : null
if (targetUser.role !== "field_admin" && !normalizedDepartment) {
return NextResponse.json({ error: "Missing required fields." }, { status: 400 })
}
if ((targetUser.role === "team_admin" || targetUser.role === "user") && !normalizedTeam) {
return NextResponse.json({ error: "Missing required fields." }, { status: 400 })
}
const effectiveDepartment = targetUser.role === "field_admin" ? null : normalizedDepartment
const effectiveTeam = targetUser.role === "field_admin" ? null : normalizedTeam
const [fieldOk, departmentOk, teamOk] = await Promise.all([ const [fieldOk, departmentOk, teamOk] = await Promise.all([
hasManagedType("field", field), hasManagedType("field", field),
hasManagedType("department", department), effectiveDepartment ? hasManagedType("department", effectiveDepartment) : Promise.resolve(true),
hasManagedType("team", team), effectiveTeam ? hasManagedType("team", effectiveTeam) : Promise.resolve(true),
]) ])
if (!fieldOk || !departmentOk || !teamOk) { if (!fieldOk || !departmentOk || !teamOk) {
@@ -52,24 +67,27 @@ export async function POST(request: NextRequest) {
const fieldRows = (await safeQuery("SELECT id FROM managed_types WHERE type = 'field' AND name = ?", [ const fieldRows = (await safeQuery("SELECT id FROM managed_types WHERE type = 'field' AND name = ?", [
field, field,
])) as Array<{ id: number }> ])) as Array<{ id: number }>
const departmentRows = (await safeQuery( const departmentRows = effectiveDepartment
? ((await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?", "SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?",
[department], [effectiveDepartment],
)) as Array<{ id: number; parentId: number | null }> )) as Array<{ id: number; parentId: number | null }>)
const teamRows = (await safeQuery( : []
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'team' AND name = ?", const teamRows = effectiveTeam
[team], ? ((await safeQuery("SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'team' AND name = ?", [
)) as Array<{ id: number; parentId: number | null }> effectiveTeam,
])) as Array<{ id: number; parentId: number | null }>)
: []
if (fieldRows.length === 0 || departmentRows.length === 0 || teamRows.length === 0) { if (fieldRows.length === 0 || (effectiveDepartment && departmentRows.length === 0) || (effectiveTeam && teamRows.length === 0)) {
return NextResponse.json({ error: "תחום, מסגרת או צוות שגויים." }, { status: 400 }) return NextResponse.json({ error: "תחום, מסגרת או צוות שגויים." }, { status: 400 })
} }
if (departmentRows[0].parentId !== fieldRows[0].id) { if (effectiveDepartment && departmentRows[0].parentId !== fieldRows[0].id) {
return NextResponse.json({ error: "מסגרת לא משוייכת לתחום הנבחר." }, { status: 400 }) return NextResponse.json({ error: "מסגרת לא משוייכת לתחום הנבחר." }, { status: 400 })
} }
if (teamRows[0].parentId !== departmentRows[0].id) { if (effectiveTeam && teamRows[0].parentId !== departmentRows[0].id) {
return NextResponse.json({ error: "צוות לא משוייך למסגרת הנבחרת." }, { status: 400 }) return NextResponse.json({ error: "צוות לא משוייך למסגרת הנבחרת." }, { status: 400 })
} }
@@ -94,7 +112,7 @@ export async function POST(request: NextRequest) {
if (targetUser.team !== admin.team) { if (targetUser.team !== admin.team) {
return NextResponse.json({ error: "המשתמש הנבחר לא משוייך לצוות שלך." }, { status: 403 }) return NextResponse.json({ error: "המשתמש הנבחר לא משוייך לצוות שלך." }, { status: 403 })
} }
if (team !== admin.team) { if (effectiveTeam !== admin.team) {
return NextResponse.json({ error: "מנהלי צוותים רשאים לנהל אך ורק את הצוות שלהם." }, { status: 403 }) return NextResponse.json({ error: "מנהלי צוותים רשאים לנהל אך ורק את הצוות שלהם." }, { status: 403 })
} }
if (admin.department && department !== admin.department) { if (admin.department && department !== admin.department) {
@@ -109,8 +127,8 @@ export async function POST(request: NextRequest) {
await safeQuery("UPDATE users SET field = ?, department = ?, team = ? WHERE national_id = ?", [ await safeQuery("UPDATE users SET field = ?, department = ?, team = ? WHERE national_id = ?", [
field, field,
department, effectiveDepartment,
team, effectiveTeam,
targetUserId, targetUserId,
]) ])

View File

@@ -12,6 +12,7 @@ export async function POST(request: Request) { // Changed to POST, similar to te
SELECT SELECT
national_id, national_id,
name, name,
role,
in_shelter, in_shelter,
last_updated, last_updated,
is_admin, is_admin,

View File

@@ -15,6 +15,7 @@ interface ManagedTypeOption {
interface UserScopeModalUser { interface UserScopeModalUser {
national_id: string national_id: string
name: string name: string
role?: string
field?: string field?: string
department?: string department?: string
team?: string team?: string
@@ -44,12 +45,18 @@ export function UserScopeModal({
const [field, setField] = useState("") const [field, setField] = useState("")
const [department, setDepartment] = useState("") const [department, setDepartment] = useState("")
const [team, setTeam] = useState("") const [team, setTeam] = useState("")
const noDepartmentValue = "__no_department__"
const noTeamValue = "__no_team__"
useEffect(() => { useEffect(() => {
if (user && isOpen) { if (user && isOpen) {
setField(user.field || "") setField(user.field || "")
if (user.role === "field_admin") {
setDepartment(user.department || noDepartmentValue)
} else {
setDepartment(user.department || "") setDepartment(user.department || "")
setTeam(user.team || "") }
setTeam(user.team || noTeamValue)
} }
}, [user, isOpen]) }, [user, isOpen])
@@ -58,25 +65,30 @@ export function UserScopeModal({
? departments.filter((item) => item.parentId === selectedFieldId) ? departments.filter((item) => item.parentId === selectedFieldId)
: departments : departments
const selectedDepartmentId = departments.find((item) => item.name === department)?.id const selectedDepartmentId = departments.find((item) => item.name === department)?.id
const availableTeams = selectedDepartmentId const availableTeams =
selectedDepartmentId && department !== noDepartmentValue
? teams.filter((item) => item.parentId === selectedDepartmentId) ? teams.filter((item) => item.parentId === selectedDepartmentId)
: teams : []
useEffect(() => { useEffect(() => {
if (field && availableDepartments.length > 0) { if (field && availableDepartments.length > 0) {
const hasDepartment = availableDepartments.some((item) => item.name === department) const hasDepartment = availableDepartments.some((item) => item.name === department)
if (!hasDepartment) { if (!hasDepartment) {
setDepartment("") setDepartment(user?.role === "field_admin" ? noDepartmentValue : "")
setTeam("") setTeam(noTeamValue)
} }
} }
}, [field, availableDepartments, department]) }, [field, availableDepartments, department])
useEffect(() => { useEffect(() => {
if (department === noDepartmentValue) {
setTeam(noTeamValue)
return
}
if (department && availableTeams.length > 0) { if (department && availableTeams.length > 0) {
const hasTeam = availableTeams.some((item) => item.name === team) const hasTeam = availableTeams.some((item) => item.name === team)
if (!hasTeam) { if (!hasTeam) {
setTeam("") setTeam(noTeamValue)
} }
} }
}, [department, availableTeams, team]) }, [department, availableTeams, team])
@@ -84,8 +96,16 @@ export function UserScopeModal({
if (!user) return null if (!user) return null
const handleSave = async () => { const handleSave = async () => {
if (!field || !department || !team) return if (!field) return
await onSave({ userId: user.national_id, field, department, team }) const normalizedDepartment = department === noDepartmentValue ? "" : department
const normalizedTeam = team === noTeamValue ? "" : team
const scopedDepartment = user.role === "field_admin" ? "" : normalizedDepartment
const scopedTeam = user.role === "field_admin" ? "" : normalizedTeam
if (user.role !== "field_admin" && !scopedDepartment) return
if ((user.role === "team_admin" || user.role === "user") && !scopedTeam) return
await onSave({ userId: user.national_id, field, department: scopedDepartment, team: scopedTeam })
} }
return ( return (
@@ -104,7 +124,7 @@ export function UserScopeModal({
<div className="space-y-2"> <div className="space-y-2">
<Select value={field} onValueChange={setField}> <Select value={field} onValueChange={setField}>
<SelectTrigger dir="rtl"> <SelectTrigger dir="rtl">
<SelectValue placeholder="בחרו תחום" /> <SelectValue placeholder="??? ????" />
</SelectTrigger> </SelectTrigger>
<SelectContent dir="rtl"> <SelectContent dir="rtl">
{fields.map((item) => ( {fields.map((item) => (
@@ -119,7 +139,7 @@ export function UserScopeModal({
<div className="space-y-2"> <div className="space-y-2">
<Select value={department} onValueChange={setDepartment}> <Select value={department} onValueChange={setDepartment}>
<SelectTrigger dir="rtl"> <SelectTrigger dir="rtl">
<SelectValue placeholder="בחרו מסגרת" /> <SelectValue placeholder="??? ?????" />
</SelectTrigger> </SelectTrigger>
<SelectContent dir="rtl"> <SelectContent dir="rtl">
{availableDepartments.map((item) => ( {availableDepartments.map((item) => (
@@ -127,6 +147,9 @@ export function UserScopeModal({
{item.name} {item.name}
</SelectItem> </SelectItem>
))} ))}
{user.role === "field_admin" && (
<SelectItem value={noDepartmentValue}>??? ?????</SelectItem>
)}
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -134,7 +157,7 @@ export function UserScopeModal({
<div className="space-y-2"> <div className="space-y-2">
<Select value={team} onValueChange={setTeam}> <Select value={team} onValueChange={setTeam}>
<SelectTrigger dir="rtl"> <SelectTrigger dir="rtl">
<SelectValue placeholder="בחרו צוות" /> <SelectValue placeholder="??? ????" />
</SelectTrigger> </SelectTrigger>
<SelectContent dir="rtl"> <SelectContent dir="rtl">
{availableTeams.map((item) => ( {availableTeams.map((item) => (
@@ -142,6 +165,7 @@ export function UserScopeModal({
{item.name} {item.name}
</SelectItem> </SelectItem>
))} ))}
<SelectItem value={noTeamValue}>ללא צוות</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@@ -153,7 +177,12 @@ export function UserScopeModal({
<Button <Button
onClick={handleSave} onClick={handleSave}
className="w-full" className="w-full"
disabled={isSaving || !field || !department || !team} disabled={
isSaving ||
!field ||
(user.role !== "field_admin" && !department) ||
((user.role === "team_admin" || user.role === "user") && !team)
}
> >
{isSaving ? "שומר..." : "שמירה"} {isSaving ? "שומר..." : "שמירה"}
</Button> </Button>

View File

@@ -1,7 +1,7 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
env: { env: {
APPVERSION: '1.1.3' APPVERSION: '1.1.4'
}, },
experimental: { experimental: {
serverComponentsExternalPackages: ['mysql2'] serverComponentsExternalPackages: ['mysql2']

View File

@@ -1,6 +1,6 @@
{ {
"name": "mamad-app", "name": "mamad-app",
"version": "1.1.3", "version": "1.1.4",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "next build", "build": "next build",