feat(bookings): improve admin booking wizard and user dashboard

Backend:
- Enriched GetAllUserBookings response with calculated total_amount,
  amount_paid, and duration_minutes.
- Refactored GetBookingHandler to return a flat booking object matching
  frontend expectations.
- Added account_role to admin user list response and sorted users by
  booking activity.
- Corrected function name oo to AdminCreateBookingForUserHandler.

Frontend:
- Rebuilt BookingCreateModal into a 4-step wizard supporting guest
  bookings, service overrides, and real-time availability checks.
- Fixed account dashboard logic to correctly identify upcoming vs past
  bookings and sort unpaid items to the top.
- Extracted booking flow into a shared BookingFlow component.
- Redirected admin users from home page to /today.
This commit is contained in:
2026-02-12 22:15:10 +00:00
parent 2ace6d4d87
commit 50746595e7
34 changed files with 7688 additions and 5413 deletions
+36 -25
View File
@@ -78,10 +78,11 @@ type SocialLogin struct {
}
type UserListItem struct {
ID string `json:"id"`
FullName string `json:"fullName"`
Email *string `json:"email,omitempty"`
Phone *string `json:"phone,omitempty"`
ID string `json:"id"`
FullName string `json:"fullName"`
Email *string `json:"email,omitempty"`
Phone *string `json:"phone,omitempty"`
AccountRole string `json:"account_role"`
}
type UserListResponse struct {
@@ -386,35 +387,39 @@ func ListAdminUsersHandler(w http.ResponseWriter, r *http.Request) {
searchPattern := "%" + searchTerm + "%"
countQuery = `
SELECT COUNT(*)
FROM users
WHERE fn ILIKE $1
OR email ILIKE $1
OR phone ILIKE $1
`
SELECT COUNT(*)
FROM users
WHERE fn ILIKE $1
OR email ILIKE $1
OR phone ILIKE $1
`
countArgs = []interface{}{searchPattern}
listQuery = `
SELECT id, fn, email, phone
FROM users
WHERE fn ILIKE $1
OR email ILIKE $1
OR phone ILIKE $1
ORDER BY created_at DESC
LIMIT $2 OFFSET $3
`
SELECT u.id, u.fn, u.email, u.phone, u.account_role
FROM users u
LEFT JOIN bookings b ON u.id = b.user_id
WHERE u.fn ILIKE $1
OR u.email ILIKE $1
OR u.phone ILIKE $1
GROUP BY u.id, u.fn, u.email, u.phone, u.account_role, u.created_at
ORDER BY COUNT(b.id) DESC, u.created_at DESC
LIMIT $2 OFFSET $3
`
listArgs = []interface{}{searchPattern, perPage, offset}
} else {
// No search - get all users
// No search - get all users, sorted by booking count
countQuery = `SELECT COUNT(*) FROM users`
countArgs = []interface{}{}
listQuery = `
SELECT id, fn, email, phone
FROM users
ORDER BY created_at DESC
LIMIT $1 OFFSET $2
`
SELECT u.id, u.fn, u.email, u.phone, u.account_role
FROM users u
LEFT JOIN bookings b ON u.id = b.user_id
GROUP BY u.id, u.fn, u.email, u.phone, u.account_role, u.created_at
ORDER BY COUNT(b.id) DESC, u.created_at DESC
LIMIT $1 OFFSET $2
`
listArgs = []interface{}{perPage, offset}
}
@@ -439,7 +444,13 @@ func ListAdminUsersHandler(w http.ResponseWriter, r *http.Request) {
var users []UserListItem
for rows.Next() {
var user UserListItem
err := rows.Scan(&user.ID, &user.FullName, &user.Email, &user.Phone)
err := rows.Scan(
&user.ID,
&user.FullName,
&user.Email,
&user.Phone,
&user.AccountRole,
)
if err != nil {
log.Printf("Failed to scan user row: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)