fix(admin): pagination, patch tests, and per-page limits
Backend:
- Fix GetAllAdminBookingsHandler and SearchAdminBookingsHandler to
return totalPages in response
- Auto-record patch tests when booking status progresses to "completed"
- Add GET/POST /api/admin/users/{id}/patch-tests endpoints
Frontend:
- BookingsCard: proper pagination with 4 per page, prev/next buttons
- UsersCard, BookingCreateModal, WalkInCreateModal: per_page=4 for user
search
- Add PatchTestModal for manual patch test entry in UserModal
- Hide patch test section when user has no eligible services
Database:
- Add UNIQUE constraint on user_service_patch_tests(user_id, service_id)
This commit is contained in:
@@ -553,3 +553,108 @@ func ChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
type ServiceForPatchTest struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PatchTestDurationHours int `json:"patchTestDurationHours"`
|
||||
}
|
||||
|
||||
// GET /api/admin/users/{id}/patch-tests/eligible
|
||||
func GetEligiblePatchTestServicesHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "id")
|
||||
if userID == "" {
|
||||
http.Error(w, "User ID is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Debug: count services with patch test
|
||||
var totalWithPatchTest int
|
||||
err := db.DB.QueryRow(r.Context(), `SELECT COUNT(*) FROM services WHERE is_active = true AND patch_test_duration_hours > 0`).Scan(&totalWithPatchTest)
|
||||
if err != nil {
|
||||
log.Printf("Debug: failed to count patch test services: %v", err)
|
||||
}
|
||||
log.Printf("Debug: userID=%s, services with patch_test=%d", userID, totalWithPatchTest)
|
||||
|
||||
rows, err := db.DB.Query(r.Context(), `
|
||||
SELECT s.id, s.name, s.patch_test_duration_hours
|
||||
FROM services s
|
||||
WHERE s.is_active = true AND s.patch_test_duration_hours > 0
|
||||
AND s.id NOT IN (
|
||||
SELECT service_id FROM user_service_patch_tests WHERE user_id = $1
|
||||
)
|
||||
ORDER BY s.name ASC
|
||||
`, userID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch eligible patch test services: %v", err)
|
||||
http.Error(w, "server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var services []ServiceForPatchTest
|
||||
for rows.Next() {
|
||||
var s ServiceForPatchTest
|
||||
if err := rows.Scan(&s.ID, &s.Name, &s.PatchTestDurationHours); err != nil {
|
||||
log.Printf("Failed to scan service: %v", err)
|
||||
continue
|
||||
}
|
||||
services = append(services, s)
|
||||
}
|
||||
|
||||
if services == nil {
|
||||
services = []ServiceForPatchTest{}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(services)
|
||||
}
|
||||
|
||||
type AddPatchTestRequest struct {
|
||||
ServiceID string `json:"service_id"`
|
||||
}
|
||||
|
||||
// POST /api/admin/users/{id}/patch-tests
|
||||
func AddPatchTestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "id")
|
||||
if userID == "" {
|
||||
http.Error(w, "User ID is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req AddPatchTestRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if req.ServiceID == "" {
|
||||
http.Error(w, "service_id is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var patchTestHours int
|
||||
err := db.DB.QueryRow(r.Context(), `SELECT patch_test_duration_hours FROM services WHERE id = $1 AND is_active = true AND patch_test_duration_hours > 0`, req.ServiceID).Scan(&patchTestHours)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
http.Error(w, "service not found or does not require patch test", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Printf("Failed to verify service: %v", err)
|
||||
http.Error(w, "server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.DB.Exec(r.Context(), `
|
||||
INSERT INTO user_service_patch_tests (user_id, service_id, last_time)
|
||||
VALUES ($1, $2, NOW())
|
||||
ON CONFLICT (user_id, service_id) DO UPDATE SET last_time = NOW()
|
||||
`, userID, req.ServiceID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to add patch test: %v", err)
|
||||
http.Error(w, "failed to add patch test", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user