diff --git a/app/admin/page.tsx b/app/admin/page.tsx index d0931af..df40b2f 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,5 +1,6 @@ "use client" - +//export const dynamic = 'force-dynamic' +//export const revalidate = 0 import type React from "react" import { useState, useEffect } from "react" import { useRouter } from "next/navigation" @@ -189,7 +190,7 @@ export default function AdminPage() { if (data.lastReset.timestamp) { const resetTime = new Date(data.lastReset.timestamp).getTime() const now = new Date().getTime() - const cooldownMs = 2 * 60 * 1000 // 2 minutes + const cooldownMs = 2 * 60 // 2 minutes const remaining = Math.max(0, cooldownMs - (now - resetTime)) setGlobalResetCooldown(Math.ceil(remaining / 1000)) } @@ -315,13 +316,6 @@ export default function AdminPage() { setUser(parsedUser) }, [router]) - // Add this right after the existing useEffect that sets the user - useEffect(() => { - // Force initial fetch when user changes - if (user?.national_id) { - refetchGlobal() - } - }, [user?.national_id, refetchGlobal]) useEffect(() => { if (globalResetCooldown > 0) { const timer = setTimeout(() => setGlobalResetCooldown(globalResetCooldown - 1), 1000) @@ -364,7 +358,7 @@ export default function AdminPage() { if (response.ok) { setMessage(data.message || "כל הסטטוסים אופסו בהצלחה") - setGlobalResetCooldown(120) // 2 minutes + setGlobalResetCooldown(30) // 2 minutes setGlobalLastReset(`${user?.name} - ${new Date().toLocaleString("he-IL")}`) refetchGlobal() refetchTeam() // Also refresh team data @@ -642,13 +636,16 @@ export default function AdminPage() { setFieldModalOpen(true) } - const handleManualRefresh = () => { + const handleManualRefresh = async () => { setIsRefreshing(true) - refetchGlobal() - refetchTeam() - refetchDepartment() - refetchField() - setTimeout(() => setIsRefreshing(false), 500) + try { + await Promise.all([refetchGlobal(), refetchTeam(), refetchDepartment(), refetchField()]) + } catch (error) { + console.error("Manual refresh failed:", error) + setMessage("שגיאה ברענון הנתונים") + } finally { + setTimeout(() => setIsRefreshing(false), 500) + } } const handleReportOnBehalf = async (userId: string, status: string) => { @@ -952,7 +949,10 @@ export default function AdminPage() { Debug Info:
+
User: {user?.name} ({user?.role})
Team: {teamName}
+
Global Users: {globalUsers.length}
+
Global Connected: {globalConnected ? "Yes" : "No"}
Global Reset Cooldown: {globalResetCooldown} seconds
Team Reset Cooldown: {teamResetCooldown} seconds
Department Reset Cooldown: {departmentResetCooldown} seconds
diff --git a/app/api/admin/stats/route.ts b/app/api/admin/stats/route.ts index 24a2167..5ae745d 100644 --- a/app/api/admin/stats/route.ts +++ b/app/api/admin/stats/route.ts @@ -1,8 +1,12 @@ import { NextResponse } from "next/server" import { executeQuery } from "@/lib/database" -export async function GET() { +export async function POST(request: Request) { // Changed to POST, similar to team_stats-route.ts try { + // We can still optionally parse the request body even if it's not strictly used, + // to maintain structural similarity to team_stats-route.ts. + // const {} = await request.json(); // If no data is expected, this line can be omitted or left as is. + const results = (await executeQuery(` SELECT SUM(CASE WHEN in_shelter IS NULL THEN 1 ELSE 0 END) as no_report, @@ -15,7 +19,7 @@ export async function GET() { return NextResponse.json(results[0]) } catch (error) { - console.error("Stats error:", error) - return NextResponse.json({ error: "שגיאה בטעינת סטטיסטיקות" }, { status: 500 }) + console.error("Stats error:", error) // Error logging similar to team_stats-route.ts + return NextResponse.json({ error: "שגיאה בטעינת סטטיסטיקות" }, { status: 500 }) // Error response format similar to team_stats-route.ts } -} +} \ No newline at end of file diff --git a/app/api/admin/users-by-category/route.ts b/app/api/admin/users-by-category/route.ts index 8a25bfb..4801b24 100644 --- a/app/api/admin/users-by-category/route.ts +++ b/app/api/admin/users-by-category/route.ts @@ -1,45 +1,44 @@ -import { type NextRequest, NextResponse } from "next/server"; -import { executeQuery } from "@/lib/database"; +// /api/admin/users-by-category/route.ts +import { type NextRequest, NextResponse } from "next/server" +import { executeQuery } from "@/lib/database" -export async function GET(request: NextRequest) { +export async function POST(request: NextRequest) { // This function uses POST method try { - const { searchParams } = new URL(request.url); - const category = searchParams.get("category"); + const { category } = await request.json() // Get category from request body - let query = ""; - // No params needed for these queries, as there are no WHERE conditions - // that use parameters other than the in_shelter status itself. - let params: any[] = []; + if (!category) { + return NextResponse.json({ error: "נתונים חסרים: קטגוריה" }, { status: 400 }) + } + + let query = "" + // Params array is still empty as there are no WHERE conditions that use parameters other than the in_shelter status itself. + // The category value is directly inserted into the query via the switch statement, + // which is generally safe for a limited, predefined set of categories (enum-like values). switch (category) { case "no_report": - // Added 'department', 'team', 'field' to SELECT clause - query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter IS NULL ORDER BY name"; - break; + query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter IS NULL ORDER BY name" + break case "in_shelter": - // Added 'department', 'team', 'field' to SELECT clause - query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'yes' ORDER BY name"; - break; + query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'yes' ORDER BY name" + break case "not_in_shelter": - // Added 'department', 'team', 'field' to SELECT clause - query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'no' ORDER BY name"; - break; + query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'no' ORDER BY name" + break case "no_alarm": - // Added 'department', 'team', 'field' to SELECT clause - query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'no_alarm' ORDER BY name"; - break; + query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'no_alarm' ORDER BY name" + break case "safe_after_exit": - // Added 'department', 'team', 'field' to SELECT clause - query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'safe_after_exit' ORDER BY name"; - break; + query = "SELECT national_id, name, department, team, field FROM users WHERE in_shelter = 'safe_after_exit' ORDER BY name" + break default: - return NextResponse.json({ error: "קטגוריה לא תקינה" }, { status: 400 }); + return NextResponse.json({ error: "קטגוריה לא תקינה" }, { status: 400 }) } - const users = (await executeQuery(query, params)) as any[]; - return NextResponse.json(users); + const users = (await executeQuery(query)) as any[] // Execute query without explicit params if values are hardcoded + return NextResponse.json(users) } catch (error) { - console.error("Get users by category error:", error); - return NextResponse.json({ error: "שגיאה בטעינת משתמשים" }, { status: 500 }); + console.error("Get users by category error:", error) + return NextResponse.json({ error: "שגיאה בטעינת משתמשים לפי קטגוריה" }, { status: 500 }) } } \ No newline at end of file diff --git a/app/api/admin/users/route.ts b/app/api/admin/users/route.ts index b0dd7d6..470f629 100644 --- a/app/api/admin/users/route.ts +++ b/app/api/admin/users/route.ts @@ -1,10 +1,15 @@ +// route.ts import { NextResponse } from "next/server" -import { safeQuery } from "@/lib/database" +import { executeQuery } from "@/lib/database" -export async function GET() { +export async function POST(request: Request) { // Changed to POST, similar to team_users-route.ts try { - const users = (await safeQuery(` - SELECT + // We can optionally parse the request body, even if it's not strictly used, + // to maintain structural similarity to team_users-route.ts. + // const {} = await request.json(); // If no data is expected, this line can be omitted or left as is. + + const users = (await executeQuery(` + SELECT national_id, name, in_shelter, @@ -21,7 +26,7 @@ export async function GET() { return NextResponse.json(users) } catch (error) { - console.error("Get users error:", error) - return NextResponse.json({ error: "שגיאה בטעינת משתמשים" }, { status: 500 }) + 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 } } \ No newline at end of file diff --git a/components/user-category-modal.tsx b/components/user-category-modal.tsx index 6469c4c..4b0e209 100644 --- a/components/user-category-modal.tsx +++ b/components/user-category-modal.tsx @@ -1,3 +1,4 @@ +// /components/user-category-modal.tsx "use client" import { useState, useEffect } from "react" @@ -32,7 +33,13 @@ export function UserCategoryModal({ isOpen, onClose, category, categoryName }: U const fetchUsers = async () => { setLoading(true) try { - const response = await fetch(`/api/admin/users-by-category?category=${category}`) + const response = await fetch(`/api/admin/users-by-category`, { + method: "POST", // Changed from GET to POST + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ category }), // Sending category in the request body + }) const data = await response.json() setUsers(data) } catch (err) { @@ -56,16 +63,15 @@ export function UserCategoryModal({ isOpen, onClose, category, categoryName }: U
{users.length > 0 ? ( - {/* Removed text-center and dir="rtl" here to let individual heads control alignment */} - {/* Removed text-center and dir="rtl" here to let individual heads control alignment */} - {/* Added text-right to align column headers properly for RTL */} + + שם תחום מסגרת צוות - {/* Keep text-right for cell content alignment */} + {users.map((user) => ( {user.name} diff --git a/hooks/useRealTimeUpdates.ts b/hooks/useRealTimeUpdates.ts index 7f8a3f9..1291c21 100644 --- a/hooks/useRealTimeUpdates.ts +++ b/hooks/useRealTimeUpdates.ts @@ -1,6 +1,7 @@ +// useRealTimeUpdates.ts "use client" -import { useEffect, useState, useRef } from "react" +import { useEffect, useState, useRef } from "react" // Removed useCallback as it's not in useTeamRealTimeUpdates interface UpdateData { stats?: any @@ -13,21 +14,48 @@ export function useRealTimeUpdates(onUpdate?: (data: UpdateData) => void) { const intervalRef = useRef() const lastDataRef = useRef("") - const fetchUpdates = async () => { + const fetchUpdates = async () => { // No longer a useCallback, similar to useTeamRealTimeUpdates try { - // Fetch all data in parallel - const [statsRes, usersRes, resetRes] = await Promise.all([ - fetch("/api/admin/stats"), - fetch("/api/admin/users"), - fetch("/api/admin/last-reset"), + // Fetch all data in parallel, similar to how useTeamRealTimeUpdates fetches stats and users + const [statsRes, usersRes, lastResetRes] = await Promise.all([ + fetch("/api/admin/stats", { // Using relative paths as in useTeamRealTimeUpdates + method: "POST", // This was changed to POST in a previous step + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache", // Kept this header from original useRealTimeUpdates for completeness + }, + // If your backend POST endpoint expects a body, you would add it here. + // For example, body: JSON.stringify({ /* some_data: 'value' */ }) + }), + fetch("/api/admin/users", { // Using relative paths as in useTeamRealTimeUpdates + method: "POST", // Changed from GET to POST as requested + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache", // Kept this header from original useRealTimeUpdates for completeness + }, + // If the POST endpoint expects a body, you'll need to add it here, e.g.: + // body: JSON.stringify({ /* any required data */ }), + }), + fetch("/api/admin/last-reset", { // Using relative paths as in useTeamRealTimeUpdates + method: "GET", + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache", // Kept this header from original useRealTimeUpdates for completeness + }, + }), ]) - const [stats, users, lastReset] = await Promise.all([statsRes.json(), usersRes.json(), resetRes.json()]) + // Parse JSON data, similar to how useTeamRealTimeUpdates handles responses + const [stats, users, lastReset] = await Promise.all([ + statsRes.json(), + usersRes.json(), + lastResetRes.json(), + ]) const newData = { stats, users, lastReset } const newDataString = JSON.stringify(newData) - // Only trigger update if data actually changed + // Only trigger update if data actually changed, identical logic to useTeamRealTimeUpdates if (newDataString !== lastDataRef.current) { lastDataRef.current = newDataString if (onUpdate) { @@ -37,24 +65,25 @@ export function useRealTimeUpdates(onUpdate?: (data: UpdateData) => void) { setIsConnected(true) } catch (err) { - console.error("Error fetching updates:", err) + console.error("Error fetching updates:", err) // Error logging similar to useTeamRealTimeUpdates setIsConnected(false) } } useEffect(() => { - // Initial fetch + // Initial fetch, similar to useTeamRealTimeUpdates fetchUpdates() - // Set up polling every 2 seconds + // Set up polling every 2 seconds, identical to useTeamRealTimeUpdates intervalRef.current = setInterval(fetchUpdates, 2000) return () => { + // Cleanup function, identical to useTeamRealTimeUpdates if (intervalRef.current) { clearInterval(intervalRef.current) } } - }, []) + }, []) // Removed adminId dependency and fetchUpdates dependency from useEffect, mirroring useTeamRealTimeUpdates' useEffect dependencies - return { isConnected, refetch: fetchUpdates } -} + return { isConnected, refetch: fetchUpdates } // Return value identical to useTeamRealTimeUpdates +} \ No newline at end of file diff --git a/lib/api-wrapper.ts b/lib/api-wrapper.ts new file mode 100644 index 0000000..886c201 --- /dev/null +++ b/lib/api-wrapper.ts @@ -0,0 +1,37 @@ +// Utility to handle API calls in both dev and production +export async function apiCall(endpoint: string, options: RequestInit = {}) { + const baseUrl = typeof window !== "undefined" ? window.location.origin : "" + const url = `${baseUrl}${endpoint}` + + const defaultOptions: RequestInit = { + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache", + }, + ...options, + } + + try { + const response = await fetch(url, defaultOptions) + + if (!response.ok) { + throw new Error(`API call failed: ${response.status} ${response.statusText}`) + } + + const text = await response.text() + + if (!text) { + return null + } + + try { + return JSON.parse(text) + } catch (parseError) { + console.error("JSON parse error:", parseError, "Response text:", text) + throw new Error("Invalid JSON response") + } + } catch (error) { + console.error(`API call to ${endpoint} failed:`, error) + throw error + } +} diff --git a/next.config.mjs b/next.config.mjs index 8532713..7282674 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,7 +2,7 @@ const nextConfig = { env: { HOSTNAME: process.env.HOSTNAME || 'vmx-k3s-01', - APPVERSION: '1.0.3' + APPVERSION: '1.0.4' }, experimental: { serverComponentsExternalPackages: ['mysql2'] @@ -16,6 +16,11 @@ const nextConfig = { images: { unoptimized: true, }, + swcMinify: true, + poweredByHeader: false, + async rewrites() { + return [] + }, } export default nextConfig diff --git a/package-lock.json b/package-lock.json index 543ffd3..f8862aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mamad-app", - "version": "0.73.0", + "version": "1.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mamad-app", - "version": "0.73.0", + "version": "1.0.3", "dependencies": { "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "1.2.2", diff --git a/package.json b/package.json index cfdb287..2298be6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mamad-app", - "version": "1.0.3", + "version": "1.0.4", "private": true, "scripts": { "build": "next build",