Compare commits

..

5 Commits

Author SHA1 Message Date
458a78795d 1.1.4 - updated user modals 2026-01-16 23:44:13 +02:00
a5e9072b9d updated version 1.1.3 - updated hostname 2026-01-16 20:37:19 +02:00
ca6d2d7abe bbbbb 2026-01-16 20:36:21 +02:00
8a4b3bc396 aaaaa 2026-01-16 19:42:51 +02:00
a75b8ec6af version 1.1.3 - updated year on copyright notice 2026-01-16 19:41:24 +02:00
73 changed files with 303 additions and 181 deletions

View File

@@ -1,3 +0,0 @@
{
"pages": {}
}

View File

@@ -1,16 +0,0 @@
{
"polyfillFiles": [
"static/chunks/polyfills.js"
],
"devFiles": [],
"ampDevFiles": [],
"lowPriorityFiles": [
"static/development/_buildManifest.js",
"static/development/_ssgManifest.js"
],
"rootMainFiles": [],
"pages": {
"/_app": []
},
"ampFirstPages": []
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +0,0 @@
{"type": "commonjs"}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
self.__INTERCEPTION_ROUTE_REWRITE_MANIFEST="[]"

View File

@@ -1,18 +0,0 @@
self.__BUILD_MANIFEST = {
"polyfillFiles": [
"static/chunks/polyfills.js"
],
"devFiles": [],
"ampDevFiles": [],
"lowPriorityFiles": [],
"rootMainFiles": [],
"pages": {
"/_app": []
},
"ampFirstPages": []
};
self.__BUILD_MANIFEST.lowPriorityFiles = [
"/static/" + process.env.__NEXT_BUILD_ID + "/_buildManifest.js",
,"/static/" + process.env.__NEXT_BUILD_ID + "/_ssgManifest.js",
];

View File

@@ -1,6 +0,0 @@
{
"version": 3,
"middleware": {},
"functions": {},
"sortedMiddleware": []
}

View File

@@ -1 +0,0 @@
self.__REACT_LOADABLE_MANIFEST="{}"

View File

@@ -1 +0,0 @@
self.__NEXT_FONT_MANIFEST="{\"pages\":{},\"app\":{},\"appUsingSizeAdjust\":false,\"pagesUsingSizeAdjust\":false}"

View File

@@ -1 +0,0 @@
{"pages":{},"app":{},"appUsingSizeAdjust":false,"pagesUsingSizeAdjust":false}

View File

@@ -1 +0,0 @@
{}

View File

@@ -1 +0,0 @@
self.__RSC_SERVER_MANIFEST="{\n \"node\": {},\n \"edge\": {},\n \"encryptionKey\": \"process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY\"\n}"

View File

@@ -1,5 +0,0 @@
{
"node": {},
"edge": {},
"encryptionKey": "4wbouw6RrCkiRBuhNA0RdY1ZJ0IOxBnrfMXrawt4S9g="
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
self.__BUILD_MANIFEST = {__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},sortedPages:["\u002F_app"]};self.__BUILD_MANIFEST_CB && self.__BUILD_MANIFEST_CB()

View File

@@ -1 +0,0 @@
self.__SSG_MANIFEST=new Set;self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"type": "module"}

View File

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

View File

@@ -21,44 +21,52 @@ export async function POST(request: NextRequest) {
const { name, isAdmin, field, department, team, role } = await request.json()
// Input validation
if (!name || !field || !department || !team) {
return NextResponse.json({ error: "נתונים חסרים" }, { status: 400 })
if (!name || !field) {
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))) {
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 })
}
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 })
}
const fieldRows = (await safeQuery("SELECT id FROM managed_types WHERE type = 'field' AND name = ?", [
field,
])) as Array<{ id: number }>
const departmentRows = (await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?",
[department],
)) 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 = ?",
[team],
)) as Array<{ id: number; parentId: number | null }>
const departmentRows = normalizedDepartment
? ((await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?",
[normalizedDepartment],
)) as Array<{ id: number; parentId: number | null }>)
: []
const teamRows = normalizedTeam
? ((await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'team' AND name = ?",
[normalizedTeam],
)) 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 })
}
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 })
}
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 })
}
@@ -68,7 +76,21 @@ export async function POST(request: NextRequest) {
const userRole: UserRole = (role as UserRole) || (isAdmin ? "global_admin" : "user")
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
@@ -80,7 +102,7 @@ export async function POST(request: NextRequest) {
await safeQuery(
"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({

View File

@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
// Get admin's field and department
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],
)) as any[]
@@ -19,10 +19,14 @@ export async function POST(request: NextRequest) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 })
return NextResponse.json({ error: "Department is not assigned." }, { status: 400 })
}
// Check cooldown for department resets

View File

@@ -11,7 +11,7 @@ export async function POST(request: Request) {
// Get admin's field and department
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],
)) as any[]
@@ -19,10 +19,18 @@ export async function POST(request: Request) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 })
if (adminRole !== "department_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment) {
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

View File

@@ -11,7 +11,7 @@ export async function POST(request: NextRequest) {
// Get admin's field and department
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],
)) as any[]
@@ -19,10 +19,10 @@ export async function POST(request: NextRequest) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 })
if (adminRole !== "department_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment) {
return NextResponse.json([])
}
let query = ""

View File

@@ -11,7 +11,7 @@ export async function POST(request: Request) {
// Get admin's field and department
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],
)) as any[]
@@ -19,10 +19,10 @@ export async function POST(request: Request) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום ומסגרת" }, { status: 400 })
if (adminRole !== "department_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment) {
return NextResponse.json({ users: [], field: adminField, department: adminDepartment })
}
// Get department users with field and department context
@@ -31,6 +31,7 @@ export async function POST(request: Request) {
SELECT
national_id,
name,
role,
in_shelter,
last_updated,
is_admin,

View File

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

View File

@@ -12,7 +12,7 @@ export async function POST(request: NextRequest) {
// Get admin's field, department, and team
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],
)) as any[]
@@ -20,10 +20,14 @@ export async function POST(request: NextRequest) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 })
return NextResponse.json({ error: "Team is not assigned." }, { status: 400 })
}
await safeQuery(

View File

@@ -11,7 +11,7 @@ export async function POST(request: Request) {
// 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'",
"SELECT role, field, department, team FROM users WHERE national_id = ? AND role IS NOT NULL AND role != 'user'",
[adminId],
)) as any[]
@@ -19,10 +19,19 @@ export async function POST(request: Request) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 })
if (adminRole !== "team_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment || !adminTeam) {
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)

View File

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

View File

@@ -10,7 +10,7 @@ export async function POST(request: Request) {
}
// 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,
])) as any[]
@@ -18,10 +18,10 @@ export async function POST(request: Request) {
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) {
return NextResponse.json({ error: "למנהל לא הוגדרו תחום, מסגרת וצוות" }, { status: 400 })
if (adminRole !== "team_admin" && adminRole !== "global_admin" || !adminField || !adminDepartment || !adminTeam) {
return NextResponse.json({ users: [], field: adminField, department: adminDepartment, team: adminTeam })
}
// Get team users with full context (field + department + team)
@@ -30,6 +30,7 @@ export async function POST(request: Request) {
SELECT
national_id,
name,
role,
in_shelter,
last_updated,
is_admin,

View File

@@ -18,7 +18,7 @@ export async function POST(request: NextRequest) {
try {
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 })
}
@@ -31,7 +31,7 @@ export async function POST(request: NextRequest) {
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,
])) as any[]
if (userRows.length === 0) {
@@ -39,10 +39,25 @@ export async function POST(request: NextRequest) {
}
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([
hasManagedType("field", field),
hasManagedType("department", department),
hasManagedType("team", team),
effectiveDepartment ? hasManagedType("department", effectiveDepartment) : Promise.resolve(true),
effectiveTeam ? hasManagedType("team", effectiveTeam) : Promise.resolve(true),
])
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 = ?", [
field,
])) as Array<{ id: number }>
const departmentRows = (await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?",
[department],
)) 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 = ?",
[team],
)) as Array<{ id: number; parentId: number | null }>
const departmentRows = effectiveDepartment
? ((await safeQuery(
"SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'department' AND name = ?",
[effectiveDepartment],
)) as Array<{ id: number; parentId: number | null }>)
: []
const teamRows = effectiveTeam
? ((await safeQuery("SELECT id, parent_id AS parentId FROM managed_types WHERE type = 'team' AND name = ?", [
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 })
}
if (departmentRows[0].parentId !== fieldRows[0].id) {
if (effectiveDepartment && departmentRows[0].parentId !== fieldRows[0].id) {
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 })
}
@@ -94,7 +112,7 @@ export async function POST(request: NextRequest) {
if (targetUser.team !== admin.team) {
return NextResponse.json({ error: "המשתמש הנבחר לא משוייך לצוות שלך." }, { status: 403 })
}
if (team !== admin.team) {
if (effectiveTeam !== admin.team) {
return NextResponse.json({ error: "מנהלי צוותים רשאים לנהל אך ורק את הצוות שלהם." }, { status: 403 })
}
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 = ?", [
field,
department,
team,
effectiveDepartment,
effectiveTeam,
targetUserId,
])

View File

@@ -12,6 +12,7 @@ export async function POST(request: Request) { // Changed to POST, similar to te
SELECT
national_id,
name,
role,
in_shelter,
last_updated,
is_admin,
@@ -29,4 +30,4 @@ export async function POST(request: Request) { // Changed to POST, similar to te
console.error("Get users error:", error) // Error logging similar to team_users-route.ts
return NextResponse.json({ error: "שגיאה בטעינת משתמשים" }, { status: 500 }) // Error response format similar to team_users-route.ts
}
}
}

View File

@@ -1,9 +1,15 @@
import { NextResponse } from "next/server"
import os from "os"
export const dynamic = "force-dynamic"
export const revalidate = 0
export async function GET() {
try {
return NextResponse.json({ instance: os.hostname() })
return NextResponse.json(
{ instance: os.hostname() },
{ headers: { "Cache-Control": "no-store" } },
)
} catch (error) {
console.error("Instance fetch error:", error)
return NextResponse.json({ error: "Failed to load instance name." }, { status: 500 })

View File

@@ -216,7 +216,7 @@ export default function DashboardPage() {
<br/>
גרסה: {process.env.APPVERSION || "לא הוגדרה גרסה ב-Dockerfile!"}
<br/>
2025 COPYRIGHT TR-WEB
2026 COPYRIGHT TR-WEB
</div>
</CardContent>
</Card>

View File

@@ -1,9 +1,12 @@
import type React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { IBM_Plex_Sans_Hebrew } from "next/font/google"
import "./globals.css"
const inter = Inter({ subsets: ["latin"] })
const ibmPlexSansHebrew = IBM_Plex_Sans_Hebrew({
subsets: ["hebrew"],
weight: ["100", "200", "300", "400", "500", "600", "700"],
})
export const metadata: Metadata = {
title: 'ממ"ד',
@@ -22,7 +25,7 @@ export default function RootLayout({
}) {
return (
<html lang="he" dir="rtl">
<body className={inter.className}>{children}</body>
<body className={ibmPlexSansHebrew.className}>{children}</body>
</html>
)
}

View File

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

View File

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

View File

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

View File

@@ -2,9 +2,6 @@
@tailwind components;
@tailwind utilities;
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {