Split admin dashboard, implement user and booking search
This commit is contained in:
@@ -170,10 +170,11 @@ type GetAllBookingsRequest struct {
|
||||
|
||||
// BookingListResponse represents a paginated list of bookings
|
||||
type BookingListResponse struct {
|
||||
Bookings []Booking `json:"bookings"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"per_page"`
|
||||
Total int `json:"total"`
|
||||
Bookings []Booking `json:"bookings"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PerPage int `json:"perPage"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
}
|
||||
|
||||
// SearchBookingsRequest represents search parameters
|
||||
@@ -573,76 +574,53 @@ func GetAllBookingsByUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
req := parseGetAllBookingsRequest(r)
|
||||
// Parse pagination parameters
|
||||
query := r.URL.Query()
|
||||
page := 1
|
||||
perPage := 5
|
||||
|
||||
// Build query with specific user filter
|
||||
baseQuery := `
|
||||
SELECT id, user_id, start_time, status, notes, created_at, updated_at, created_by,
|
||||
u.fn, u.profile_pic_url, u.notes as user_notes
|
||||
FROM bookings b
|
||||
LEFT JOIN users u ON b.user_id = u.id
|
||||
WHERE b.user_id = $1
|
||||
`
|
||||
|
||||
countQuery := `SELECT COUNT(*) FROM bookings WHERE user_id = $1`
|
||||
var args []interface{}
|
||||
args = append(args, userID)
|
||||
paramCount := 2
|
||||
|
||||
// Add filters
|
||||
if req.Status != nil {
|
||||
baseQuery += fmt.Sprintf(" AND b.status = $%d", paramCount)
|
||||
countQuery += fmt.Sprintf(" AND status = $%d", paramCount)
|
||||
args = append(args, *req.Status)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if req.StartDate != nil {
|
||||
baseQuery += fmt.Sprintf(" AND b.start_time >= $%d", paramCount)
|
||||
countQuery += fmt.Sprintf(" AND start_time >= $%d", paramCount)
|
||||
startTime, err := time.ParseInLocation("2006-01-02", *req.StartDate, londonLocation)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid start_date format, use YYYY-MM-DD", http.StatusBadRequest)
|
||||
return
|
||||
if pageStr := query.Get("page"); pageStr != "" {
|
||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
// Ensure it's at start of day in London time
|
||||
startTime = time.Date(startTime.Year(), startTime.Month(), startTime.Day(), 0, 0, 0, 0, londonLocation)
|
||||
args = append(args, startTime)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
if req.EndDate != nil {
|
||||
baseQuery += fmt.Sprintf(" AND b.start_time <= $%d", paramCount)
|
||||
countQuery += fmt.Sprintf(" AND start_time <= $%d", paramCount)
|
||||
endTime, err := time.Parse("2006-01-02", *req.EndDate)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid end_date format, use YYYY-MM-DD", http.StatusBadRequest)
|
||||
return
|
||||
if perPageStr := query.Get("per_page"); perPageStr != "" {
|
||||
if pp, err := strconv.Atoi(perPageStr); err == nil && pp > 0 && pp <= 100 {
|
||||
perPage = pp
|
||||
}
|
||||
endTime = endTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
|
||||
args = append(args, endTime)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
// Add ordering and pagination
|
||||
baseQuery += " ORDER BY b.start_time ASC"
|
||||
if req.PerPage > 0 {
|
||||
baseQuery += fmt.Sprintf(" LIMIT $%d OFFSET $%d", paramCount, paramCount+1)
|
||||
args = append(args, req.PerPage, (req.Page-1)*req.PerPage)
|
||||
}
|
||||
offset := (page - 1) * perPage
|
||||
|
||||
// Get total count
|
||||
var total int
|
||||
err := db.DB.QueryRow(r.Context(), countQuery, args[:1]...).Scan(&total)
|
||||
err := db.DB.QueryRow(r.Context(), `
|
||||
SELECT COUNT(*)
|
||||
FROM bookings
|
||||
WHERE user_id = $1
|
||||
`, userID).Scan(&total)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get booking count for user %s: %v", userID, err)
|
||||
log.Printf("Failed to count bookings for user %s: %v", userID, err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get bookings
|
||||
rows, err := db.DB.Query(r.Context(), baseQuery, args...)
|
||||
// Fetch bookings with pagination, ordered by start_time DESC (future to past)
|
||||
rows, err := db.DB.Query(r.Context(), `
|
||||
SELECT
|
||||
b.id,
|
||||
b.start_time,
|
||||
b.status,
|
||||
b.notes,
|
||||
b.created_at,
|
||||
b.updated_at,
|
||||
b.created_by
|
||||
FROM bookings b
|
||||
WHERE b.user_id = $1
|
||||
ORDER BY b.start_time DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`, userID, perPage, offset)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch bookings for user %s: %v", userID, err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
@@ -653,45 +631,81 @@ func GetAllBookingsByUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var bookings []Booking
|
||||
for rows.Next() {
|
||||
var b Booking
|
||||
b.User = &UserSummary{}
|
||||
var createdBy sql.NullString
|
||||
|
||||
var userFullName, userPicURL, userNotes sql.NullString
|
||||
err := rows.Scan(
|
||||
&b.ID, &b.User.ID, &b.StartTime, &b.Status, &b.Notes,
|
||||
&b.CreatedAt, &b.UpdatedAt, &createdBy,
|
||||
&userFullName, &userPicURL, &userNotes,
|
||||
&b.ID,
|
||||
&b.StartTime,
|
||||
&b.Status,
|
||||
&b.Notes,
|
||||
&b.CreatedAt,
|
||||
&b.UpdatedAt,
|
||||
&b.CreatedBy,
|
||||
)
|
||||
if userFullName.Valid {
|
||||
b.User.FullName = userFullName.String
|
||||
}
|
||||
if userPicURL.Valid {
|
||||
b.User.ProfilePicURL = &userPicURL.String
|
||||
}
|
||||
if userNotes.Valid {
|
||||
b.User.Notes = &userNotes.String
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Failed to scan booking row: %v", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if createdBy.Valid {
|
||||
b.CreatedBy = &createdBy.String
|
||||
|
||||
// Fetch services for this booking
|
||||
serviceRows, err := db.DB.Query(r.Context(), `
|
||||
SELECT
|
||||
s.name,
|
||||
COALESCE(bs.override_price, s.price) as price,
|
||||
COALESCE(bs.override_duration_minutes, s.duration_minutes) as duration_minutes
|
||||
FROM booking_services bs
|
||||
LEFT JOIN services s ON bs.service_id = s.id
|
||||
WHERE bs.booking_id = $1
|
||||
`, b.ID)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch services for booking %s: %v", b.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var totalAmount float64
|
||||
for serviceRows.Next() {
|
||||
var service BookingService
|
||||
var price float64
|
||||
var durationMinutes int
|
||||
|
||||
if err := serviceRows.Scan(&service.ServiceName, &price, &durationMinutes); err != nil {
|
||||
log.Printf("Failed to scan service: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
service.Price = &price
|
||||
service.DurationMinutes = &durationMinutes
|
||||
totalAmount += price
|
||||
|
||||
b.Services = append(b.Services, service)
|
||||
}
|
||||
serviceRows.Close()
|
||||
|
||||
b.TotalAmount = totalAmount
|
||||
bookings = append(bookings, b)
|
||||
}
|
||||
|
||||
if bookings == nil {
|
||||
bookings = []Booking{}
|
||||
}
|
||||
|
||||
// Calculate total pages
|
||||
totalPages := (total + perPage - 1) / perPage
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
response := BookingListResponse{
|
||||
Bookings: bookings,
|
||||
Page: req.Page,
|
||||
PerPage: req.PerPage,
|
||||
Total: total,
|
||||
Bookings: bookings,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PerPage: perPage,
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
log.Printf("Failed to encode response: %v", err)
|
||||
log.Printf("Failed to encode bookings response: %v", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -773,7 +787,7 @@ func GetAdminBookingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
defer serviceRows.Close()
|
||||
|
||||
var totalAmount float64
|
||||
var durationMinutes int
|
||||
var durationMinutesTotal int
|
||||
|
||||
for serviceRows.Next() {
|
||||
var name string
|
||||
@@ -788,7 +802,7 @@ func GetAdminBookingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Calculate totals
|
||||
totalAmount += price
|
||||
durationMinutes += durationMinutes
|
||||
durationMinutesTotal += durationMinutes
|
||||
booking.Services = append(booking.Services, BookingService{
|
||||
ServiceName: &name,
|
||||
Price: &price,
|
||||
@@ -797,7 +811,7 @@ func GetAdminBookingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
booking.TotalAmount = totalAmount
|
||||
booking.DurationMinutes = durationMinutes
|
||||
booking.DurationMinutes = durationMinutesTotal
|
||||
|
||||
// ----------------------------
|
||||
// 3. Fetch payments and calculate amount paid
|
||||
|
||||
Reference in New Issue
Block a user