updated reservation details screen and filter on available screen

This commit is contained in:
umer
2025-05-07 01:11:51 +05:00
parent 00058c440c
commit f1d04650b6
25 changed files with 490 additions and 241 deletions
@@ -4,22 +4,6 @@
type = "1" type = "1"
version = "2.0"> version = "2.0">
<Breakpoints> <Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "D5681314-32C1-4998-BEBF-D4691C886BC2"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "56"
endingLineNumber = "56"
landmarkName = "getDailySched(clubId:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -52,22 +36,6 @@
landmarkType = "14"> landmarkType = "14">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "99B40998-6781-4339-ACB5-F75FF0BC38A7"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "69"
endingLineNumber = "69"
landmarkName = "getAvailability()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -84,38 +52,6 @@
landmarkType = "24"> landmarkType = "24">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "9DDEE31F-4183-4E3A-8568-5DBD8C04570A"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "84"
endingLineNumber = "84"
landmarkName = "getProfile()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "6A2DA2F1-CA9B-4BCE-8ACA-D6D14E858CDA"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "77"
endingLineNumber = "77"
landmarkName = "getProfile()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -148,22 +84,6 @@
landmarkType = "24"> landmarkType = "24">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "002CCE4B-DBB4-4BE0-82C0-03D8524CC2DA"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "34"
endingLineNumber = "34"
landmarkName = "getStats(completion:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -180,38 +100,6 @@
landmarkType = "24"> landmarkType = "24">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "C120EA6E-E899-43F2-92E6-438C52F26A83"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "98"
endingLineNumber = "98"
landmarkName = "getClubProfile()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1047CF2A-7B0C-44CA-AB60-933AFFF88D49"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/ViewModels/DashViewModel.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "41"
endingLineNumber = "41"
landmarkName = "getStats(completion:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -222,8 +110,8 @@
filePath = "Club Portal/UI/Dashboard/DashboardView.swift" filePath = "Club Portal/UI/Dashboard/DashboardView.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "65" startingLineNumber = "66"
endingLineNumber = "65" endingLineNumber = "66"
landmarkName = "body" landmarkName = "body"
landmarkType = "24"> landmarkType = "24">
</BreakpointContent> </BreakpointContent>
@@ -260,38 +148,6 @@
landmarkType = "7"> landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "9F0229B2-427C-4F86-BB4B-02C7B32822AA"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/Network/Api_Stuff/APIError.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "60"
endingLineNumber = "60"
landmarkName = "request(_:responseType:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "D4FFED32-06EF-4269-A4CC-2E38B7C48467"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/Network/Api_Stuff/APIError.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "57"
endingLineNumber = "57"
landmarkName = "request(_:responseType:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -311,33 +167,17 @@
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
uuid = "2F83466D-D868-4502-8601-E4770581F3EB" uuid = "C3CB7FC3-4DA4-47C4-88EE-3AB6FBEC9D3D"
shouldBeEnabled = "Yes" shouldBeEnabled = "Yes"
ignoreCount = "0" ignoreCount = "0"
continueAfterRunningActions = "No" continueAfterRunningActions = "No"
filePath = "Club Portal/UI/DailySecheduler/ScheduleView.swift" filePath = "Club Portal/UI/Availbility/AvailabilityCardView.swift"
startingColumnNumber = "9223372036854775807" startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807"
startingLineNumber = "241" startingLineNumber = "58"
endingLineNumber = "241" endingLineNumber = "58"
landmarkName = "eventOverlay(for:)" landmarkName = "body"
landmarkType = "7"> landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "EDD74E11-A7AD-4B5F-919B-B2DEB378DD99"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/DailySecheduler/ScheduleView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "239"
endingLineNumber = "239"
landmarkName = "eventOverlay(for:)"
landmarkType = "7">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
</Breakpoints> </Breakpoints>
@@ -1,6 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "WhatsApp Image 2024-11-29 at 00.34.09 1.jpeg",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"
@@ -12,6 +13,7 @@
"value" : "dark" "value" : "dark"
} }
], ],
"filename" : "WhatsApp Image 2024-11-29 at 00.34.09 2.jpeg",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"
@@ -23,6 +25,7 @@
"value" : "tinted" "value" : "tinted"
} }
], ],
"filename" : "WhatsApp Image 2024-11-29 at 00.34.09.jpeg",
"idiom" : "universal", "idiom" : "universal",
"platform" : "ios", "platform" : "ios",
"size" : "1024x1024" "size" : "1024x1024"
@@ -210,6 +210,98 @@ final class APIService {
} }
} }
func requestPlayers(_ endpoint: Endpoint) async throws -> Players? {
guard let request = endpoint.urlRequest else {
throw APIError.invalidURL
}
print("Request URL: \(request.url?.absoluteString ?? "Invalid URL")")
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse(0, "No HTTP response received")
}
print("HTTP Status Code: \(httpResponse.statusCode)")
if !(200..<300).contains(httpResponse.statusCode) {
if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
if errorResponse.message == "TOKEN_EXPIRED" {
// Perform logout
throw APIError.logout
}
throw APIError.serverError(errorResponse.message)
} else {
throw APIError.invalidResponse(httpResponse.statusCode, "Unknown server error")
}
}
do {
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
if let resp = Players(JSONString: responseString) {
print(resp)
return resp
}
}
return nil
} catch {
throw APIError.decodingError(error)
}
}
func requestCoach(_ endpoint: Endpoint) async throws -> CoacheList? {
guard let request = endpoint.urlRequest else {
throw APIError.invalidURL
}
print("Request URL: \(request.url?.absoluteString ?? "Invalid URL")")
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIError.invalidResponse(0, "No HTTP response received")
}
print("HTTP Status Code: \(httpResponse.statusCode)")
if !(200..<300).contains(httpResponse.statusCode) {
if let errorResponse = try? JSONDecoder().decode(ErrorResponse.self, from: data) {
if errorResponse.message == "TOKEN_EXPIRED" {
// Perform logout
throw APIError.logout
}
throw APIError.serverError(errorResponse.message)
} else {
throw APIError.invalidResponse(httpResponse.statusCode, "Unknown server error")
}
}
do {
if let responseString = String(data: data, encoding: .utf8) {
print("Response: \(responseString)")
if let resp = CoacheList(JSONString: responseString) {
print(resp)
return resp
}
}
return nil
} catch {
throw APIError.decodingError(error)
}
}
// func requestWithAlamofire<T: Decodable>(_ endpoint: Endpoint, responseType: T.Type) async throws -> T { // func requestWithAlamofire<T: Decodable>(_ endpoint: Endpoint, responseType: T.Type) async throws -> T {
// guard let urlRequest = endpoint.urlRequest else { // guard let urlRequest = endpoint.urlRequest else {
@@ -235,7 +327,7 @@ final class APIService {
// } else { // } else {
// print("Failed to convert response data to string") // print("Failed to convert response data to string")
// } // }
// //
// } else { // } else {
// throw APIError.invalidResponse(response.response?.statusCode ?? 0, error.localizedDescription) // throw APIError.invalidResponse(response.response?.statusCode ?? 0, error.localizedDescription)
// } // }
@@ -8,7 +8,6 @@
import SwiftUI import SwiftUI
struct AvailabilityListEndpoint: Endpoint { struct AvailabilityListEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String { var path: String {
"/v3/api/custom/courtmatchup/club/reservations/availability" "/v3/api/custom/courtmatchup/club/reservations/availability"
@@ -16,7 +15,19 @@ struct AvailabilityListEndpoint: Endpoint {
var method: HTTPMethod { .get } var method: HTTPMethod { .get }
let startDate: String
let endDate: String
let startTime: String
let endTime: String
var urlQueryItems: [URLQueryItem] {
[
URLQueryItem(name: "from_time", value: startTime),
URLQueryItem(name: "until_time", value: endTime),
URLQueryItem(name: "from_date", value: startDate),
URLQueryItem(name: "until_date", value: endDate)
]
}
var customHeaders: [String: String]? { nil } var customHeaders: [String: String]? { nil }
@@ -24,3 +35,51 @@ struct AvailabilityListEndpoint: Endpoint {
var isMultipart: Bool { false } var isMultipart: Bool { false }
} }
struct GetPlayerListEndpoint: Endpoint {
var path: String {
"/v4/api/records/user?order=id,desc&filter=id,in,\(playerIds)&"
}
var method: HTTPMethod { .get }
var customHeaders: [String: String]? { nil }
var urlQueryItems: [URLQueryItem] {
[
]
}
var playerIds = ""
var body: Data? { nil }
var isMultipart: Bool { false }
}
struct GetCoachListEndpoint: Endpoint {
var path: String {
"/v4/api/records/coach?join=user|user_id&order=id,desc&filter=id,in,\(playerIds)&"
}
var method: HTTPMethod { .get }
var customHeaders: [String: String]? { nil }
var urlQueryItems: [URLQueryItem] {
[
]
}
var playerIds = ""
var body: Data? { nil }
var isMultipart: Bool { false }
}
@@ -9,7 +9,7 @@ import SwiftUI
struct ReservationListEndpoint: Endpoint { struct ReservationListEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = [] var urlQueryItems: [URLQueryItem] = []
var path: String { var path: String {
"/v4/api/records/reservation?join=booking|booking_id&join=user|user_id&join=buddy|buddy_id&join=clubs|club_id&order=id,desc&filter=courtmatchup_booking.court_id,eq,163" "/v4/api/records/reservation?join=booking|booking_id&join=user|user_id&join=buddy|buddy_id&join=clubs|club_id&order=id,desc&filter=courtmatchup_booking.court_id,eq,\(clubID)"
} }
var method: HTTPMethod { .get } var method: HTTPMethod { .get }
@@ -31,7 +31,7 @@ struct Coaches: Codable {
// MARK: - CoachesAvailable // MARK: - CoachesAvailable
struct CoachesAvailable: Codable , Identifiable, Hashable { struct CoachesAvailable: Codable , Identifiable, Hashable {
let id: Int? let id = UUID().uuidString
let coachID, userID: Int? let coachID, userID: Int?
let bio, hourlyRate, availability, firstName: String? let bio, hourlyRate, availability, firstName: String?
let lastName, phone, email: String? let lastName, phone, email: String?
@@ -0,0 +1,27 @@
//
// CoacheList.swift
// Club Portal
//
// Created by Umer Tahir on 07/05/2025.
//
import Foundation
import ObjectMapper
class CoacheList : Mappable {
var list : [PlayerList]?
required init?(map: Map) {
}
func mapping(map: Map) {
list <- map["list"]
}
}
@@ -0,0 +1,90 @@
//
// Players.swift
// Club Portal
//
// Created by Umer Tahir on 07/05/2025.
//
//
// Json4Swift.swift
// Club Portal
//
// Created by Umer Tahir on 06/05/2025.
//
import Foundation
import ObjectMapper
class Players : Mappable {
var list : [UserMap]?
required init?(map: Map) {
}
func mapping(map: Map) {
list <- map["list"]
}
}
struct PlayerList : Mappable {
var id : Int?
var user_id : Int?
var name : String?
var club_id : Int?
var hourly_rate : String?
var status : Int?
var not_paid_through_platform : Int?
var completed : Int?
var type : Int?
var experience : String?
var surface_id : Int?
var bio : String?
var sport_id : String?
var availability : String?
var default_availability : String?
var week_specific_availability : String?
var account_details : String?
var photo : String?
var is_public : Int?
var create_at : String?
var update_at : String?
var user : UserMap?
init?(map: Map) {
}
mutating func mapping(map: Map) {
id <- map["id"]
user_id <- map["user_id"]
name <- map["name"]
club_id <- map["club_id"]
hourly_rate <- map["hourly_rate"]
status <- map["status"]
not_paid_through_platform <- map["not_paid_through_platform"]
completed <- map["completed"]
type <- map["type"]
experience <- map["experience"]
surface_id <- map["surface_id"]
bio <- map["bio"]
sport_id <- map["sport_id"]
availability <- map["availability"]
default_availability <- map["default_availability"]
week_specific_availability <- map["week_specific_availability"]
account_details <- map["account_details"]
photo <- map["photo"]
is_public <- map["is_public"]
create_at <- map["create_at"]
update_at <- map["update_at"]
user <- map["user"]
}
}
@@ -51,16 +51,16 @@ struct AvailabilityCardView: View {
Divider() Divider()
} }
case .coaches: case .coaches:
ForEach(coaches ?? [], id: \.self) { coach in ForEach(self.coaches ?? [], id: \.id) { coach in
HStack { HStack {
ProfileImageView(imageUrl: coach.photo ?? "", size: 30) ProfileImageView(imageUrl: coach.photo ?? "", size: 30)
Text(coach.firstName ?? "") Text("\(coach.firstName ?? "") \(coach.lastName ?? "") ")
Spacer() Spacer()
Image("send") Image("send")
.foregroundColor(.gray) .foregroundColor(.gray)
.onTapGesture { .onTapGesture {
name = coach.firstName ?? "" name = "\(coach.firstName ?? "") \(coach.lastName ?? "") "
email = coach.email ?? "" email = coach.email ?? ""
phone = coach.phone ?? "" phone = coach.phone ?? ""
showSheet.toggle() showSheet.toggle()
@@ -70,10 +70,10 @@ struct AvailabilityCardView: View {
Divider() Divider()
} }
case .staff: case .staff:
ForEach(self.stsff ?? [], id: \.self) { coach in ForEach(self.stsff ?? [], id: \.id) { coach in
HStack { HStack {
ProfileImageView(imageUrl: coach.photo ?? "", size: 30) ProfileImageView(imageUrl: coach.photo ?? "", size: 30)
Text( coach.firstName ?? "") Text("\(coach.firstName ?? "") \(coach.lastName ?? "") ")
Spacer() Spacer()
Image("send") Image("send")
.foregroundColor(.gray) .foregroundColor(.gray)
@@ -13,13 +13,16 @@ struct AvailabilityScreen: View {
@Binding var presentSideMenu: Bool // Optional for side menu @Binding var presentSideMenu: Bool // Optional for side menu
@EnvironmentObject var viewModel : DashViewModel @EnvironmentObject var viewModel : DashViewModel
@State var startDate: Date = Date()
@State var endDate: Date = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date()
@State var startTime: Date = Date()
@State var endTime: Date = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) ?? Date()
var body: some View { var body: some View {
VStack(spacing: 16) { VStack(spacing: 16) {
HeaderView(presentSideMenu: $presentSideMenu) HeaderView(presentSideMenu: $presentSideMenu)
DateSelectorView() DateSelectorView(startDate: $startDate, endDate: $endDate, startTime: $startTime, endTime: $endTime)
TabSelectorView(selectedTab: $selectedTab) TabSelectorView(selectedTab: $selectedTab)
ScrollView { ScrollView {
@@ -36,7 +39,14 @@ struct AvailabilityScreen: View {
} }
.background(Color(.systemGray6).ignoresSafeArea()) .background(Color(.systemGray6).ignoresSafeArea())
.onAppear(){ .onAppear(){
viewModel.getAvailability() viewModel.getAvailability(startDate: startDate.toCustomString, endDate: endDate.toCustomString, startTime: startTime.toTimeString, endTime: endTime.toTimeString)
} }
.onChange(of: startDate) { _ in
viewModel.getAvailability(startDate: startDate.toCustomString, endDate: endDate.toCustomString, startTime: startTime.toTimeString, endTime: endTime.toTimeString)
}
.onChange(of: endDate) { _ in
viewModel.getAvailability(startDate: startDate.toCustomString, endDate: endDate.toCustomString, startTime: startTime.toTimeString, endTime: endTime.toTimeString)
}
} }
} }
@@ -9,11 +9,11 @@ import SwiftUI
struct DateSelectorView: View { struct DateSelectorView: View {
@State private var startDate: Date = Date() @Binding var startDate: Date
@State private var endDate: Date = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date() @Binding var endDate: Date
@State private var startTime: Date = Date() @Binding var startTime: Date
@State private var endTime: Date = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) ?? Date() @Binding var endTime: Date
var body: some View { var body: some View {
VStack(spacing: 12) { VStack(spacing: 12) {
@@ -25,19 +25,21 @@ struct DetailRow: View {
} }
struct DetailsTab: View { struct DetailsTab: View {
@EnvironmentObject var viewModel : DashViewModel
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
Group { Group {
DetailRow(title: "Reservation date", value: "16 April, 2024") DetailRow(title: "Reservation date", value: viewModel.selectedReservation?.booking?.date ?? "16 April, 2024")
DetailRow(title: "Time", value: "9:00 AM 10:00 AM") DetailRow(title: "Time", value: "\(viewModel.selectedReservation?.booking?.start_time ?? "16:00:00" ) - \(viewModel.selectedReservation?.booking?.end_time ?? "16:00:00" ) ")
DetailRow(title: "Event type", value: "Lesson") DetailRow(title: "Event type", value: "Lesson")
DetailRow(title: "Sport/type", value: "Tennis • Indoors • Clay") DetailRow(title: "Sport/type", value: "\(viewModel.selectedReservation?.booking?.sport_id ?? 1)\(viewModel.selectedReservation?.booking?.type ?? "")\(viewModel.selectedReservation?.booking?.subtype ?? "") ")
DetailRow(title: "Repeating", value: "Every week") DetailRow(title: "Repeating", value: "Every week")
DetailRow(title: "Space assigned", value: "Court #5") DetailRow(title: "Space assigned", value: "Court #5")
DetailRow(title: "Court fee", value: "$50.0") DetailRow(title: "Court fee", value: "$\(viewModel.selectedReservation?.booking?.court_fee ?? "10")")
DetailRow(title: "Court fee status", value: "Paid") DetailRow(title: "Court fee status", value: "Paid")
DetailRow(title: "Note", value: "{content.note}") DetailRow(title: "Note", value: "\(viewModel.selectedReservation?.booking?.notes ?? "--")")
} }
} }
.padding() .padding()
@@ -8,6 +8,8 @@
import SwiftUI import SwiftUI
struct PlayersTab: View { struct PlayersTab: View {
@EnvironmentObject var viewModel : DashViewModel
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
@@ -9,7 +9,7 @@ struct ScheduleView: View {
// @State private var events: [ReservationList] = [] // @State private var events: [ReservationList] = []
@Binding var presentSideMenu: Bool // Optional for side menu @Binding var presentSideMenu: Bool // Optional for side menu
@State var courtName = "" @State var courtName = ""
let hours = Array(4...20) let hours = Array(0...23)
let calendar = Calendar.current let calendar = Calendar.current
@EnvironmentObject var viewModel : DashViewModel @EnvironmentObject var viewModel : DashViewModel
@@ -48,11 +48,49 @@ struct ScheduleView: View {
} }
// Time Slots with Events // Time Slots with Events
ScrollView { ScrollView {
VStack(spacing: 0) { ZStack(alignment: .topLeading) {
ForEach(hours, id: \.self) { hour in VStack(spacing: 0) {
timeSlotRow(hour: hour) ForEach(hours, id: \.self) { hour in
timeSlotRow(hour: hour)
}
} }
ForEach(viewModel.dailyReservations.filter { event in
let selectedDateStr = DateFormatter.bookingDateFormatter.string(from: selectedDate)
return event.booking?.date == selectedDateStr
}, id: \.id) { event in
if let startTime = DateFormatter.timeOnlyFormatter.date(from: event.booking?.start_time ?? ""),
let endTime = DateFormatter.timeOnlyFormatter.date(from: event.booking?.end_time ?? "") {
let startHour = calendar.component(.hour, from: startTime)
let endHour = calendar.component(.hour, from: endTime)
let yOffset = CGFloat(startHour - hours.first!) * 70
let height = max(CGFloat(endHour - startHour), 1) * 70
VStack(alignment: .leading, spacing: 2) {
HStack {
Text(event.booking?.type ?? "- -")
.font(.subheadline)
.bold()
Spacer()
}
Text("\(event.booking!.start_time!.to12HourFormat()) - \(event.booking!.end_time!.to12HourFormat())")
.font(.caption)
.foregroundColor(.gray)
}
.padding(8)
.frame(height: height)
.background(Colorr.greenColor.opacity(0.2))
.cornerRadius(12)
.foregroundColor(Colorr.greenColor.opacity(0.8))
.offset(y: yOffset)
.onTapGesture {
viewModel.selectedReservation = event
self.navigationPaths.append("ReservationDetailsView")
}
}
}
.padding(.leading, 60) // leave space for time labels
} }
.padding(.horizontal) .padding(.horizontal)
.padding(.bottom, 20) .padding(.bottom, 20)
@@ -202,14 +240,13 @@ struct ScheduleView: View {
HStack(alignment: .top) { HStack(alignment: .top) {
Text(timeLabel(for: hour)) Text(timeLabel(for: hour))
.font(.caption) .font(.caption)
.frame(width: 50, alignment: .leading) .frame(width: 55, alignment: .leading)
.padding(.top) .padding(.top)
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
Rectangle() Rectangle()
.fill(Color(.systemGray6)) .fill(Color(.systemGray6))
.frame(height: 60) .frame(height: 60)
eventOverlay(for: hour)
} }
} }
@@ -242,23 +279,36 @@ struct ScheduleView: View {
}), }),
id: \.id id: \.id
) { event in ) { event in
VStack(alignment: .leading, spacing: 2) { let height: CGFloat = {
Text(event.booking?.type ?? "- -") if let start = DateFormatter.timeOnlyFormatter.date(from: event.booking!.start_time!),
.font(.subheadline) let end = DateFormatter.timeOnlyFormatter.date(from: event.booking!.end_time!) {
.bold() let startHour = calendar.component(.hour, from: start)
Text("\(event.booking!.start_time!.to12HourFormat()) - \(event.booking!.end_time!.to12HourFormat())") let endHour = calendar.component(.hour, from: end)
.font(.caption) let duration = max(endHour - startHour, 1)
.foregroundColor(.gray) return CGFloat(duration) * 60
} }
.padding(8) return 60
.background(Colorr.greenColor.opacity(0.2)) }()
.foregroundColor(Colorr.greenColor.opacity(0.8)) Color.clear
.cornerRadius(12) .overlay(
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) VStack(alignment: .leading, spacing: 2) {
.background(Color.clear) Text(event.booking?.type ?? "- -")
.onTapGesture { .font(.subheadline)
self.navigationPaths.append("ReservationDetailsView") .bold()
} Text("\(event.booking!.start_time!.to12HourFormat()) - \(event.booking!.end_time!.to12HourFormat())")
.font(.caption)
.foregroundColor(.gray)
}
.padding(8)
.frame(height: height)
.background(Colorr.greenColor.opacity(0.2))
.cornerRadius(12)
.foregroundColor(Colorr.greenColor.opacity(0.8))
.onTapGesture {
self.navigationPaths.append("ReservationDetailsView")
},
alignment: .topLeading
)
} }
} }
.padding(.top, 4) .padding(.top, 4)
@@ -345,6 +395,14 @@ extension DateFormatter {
} }
} }
extension DateFormatter {
static var timeOnlyFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter
}
}
extension String { extension String {
func to12HourFormat() -> String { func to12HourFormat() -> String {
let inputFormatter = DateFormatter() let inputFormatter = DateFormatter()
@@ -19,9 +19,9 @@ struct AnalyticsView: View {
// SegmentedControlView() // SegmentedControlView()
ScrollView { ScrollView {
VStack(spacing: 15) { VStack(spacing: 15) {
// PieChartCard(data: self.viewModel.statResponse?.revenueByModule ?? []) PieChartCard(data: self.viewModel.statResponse?.revenueByModule ?? [])
// BarChartCard(data: self.viewModel.statResponse?.revenueHeatMap ?? []) // BarChartCard(data: self.viewModel.statResponse?.revenueHeatMap ?? [])
PieChartCard1() // PieChartCard1()
BarChartCard1() BarChartCard1()
} }
.padding(.horizontal) .padding(.horizontal)
@@ -15,6 +15,7 @@ struct SummaryView: View {
@EnvironmentObject var viewModel : DashViewModel @EnvironmentObject var viewModel : DashViewModel
@Binding var presentSideMenu: Bool // Optional for side menu @Binding var presentSideMenu: Bool // Optional for side menu
@State var filterCount = 0 @State var filterCount = 0
var body: some View { var body: some View {
NavigationStack(path: $navigationPaths) { NavigationStack(path: $navigationPaths) {
VStack { VStack {
@@ -26,7 +27,7 @@ struct SummaryView: View {
if selectedTab == 0 { if selectedTab == 0 {
FilterView(navigationPaths: $navigationPaths, filterCount: $filterCount) FilterView(navigationPaths: $navigationPaths, filterCount: $filterCount)
CourtUtilizationView() CourtUtilizationView(utilization: "\(self.viewModel.statResponse?.courtUtilization.first?.utilization_percentage ?? 10.0)%" )
ScrollView { ScrollView {
LazyVStack(spacing: 10) { LazyVStack(spacing: 10) {
@@ -54,9 +55,9 @@ struct SummaryView: View {
.navigationBarHidden(true) .navigationBarHidden(true)
.navigationDestination(for: String.self) { value in .navigationDestination(for: String.self) { value in
if value == "FilterScreen" { if value == "FilterScreen" {
FilterScreen(navigationPaths: $navigationPaths) { filter, filterCount in FilterScreen(navigationPaths: $navigationPaths) { startDate, endDate, startTime, endTime, filterCount in
self.filterCount = filterCount self.filterCount = filterCount
viewModel.getStats(completion: {}) viewModel.getStats ( startDate: startDate, endDate: endDate, startTime: startTime, endTime: endTime, completion: {})
} }
} }
} }
@@ -68,7 +69,7 @@ struct SummaryView: View {
} }
} }
// viewModel.getClubProfile() // viewModel.getClubProfile()
viewModel.getProfile() // viewModel.getProfile()
} }
@@ -83,16 +84,14 @@ struct HeaderView: View {
var body: some View { var body: some View {
HStack { HStack {
Image(systemName: "person.circle.fill") ProfileImageView(imageUrl: AppSettings.clubLogo, size: 32)
.resizable() .onTapGesture {
.frame(width: 32, height: 32)
.onTapGesture {
presentSideMenu.toggle() presentSideMenu.toggle()
} }
Spacer() Spacer()
Text("The Royal Club") Text(AppSettings.clubName)
.font(.headline) .font(.headline)
Spacer() Spacer()
@@ -199,13 +198,14 @@ struct FilterView: View {
// MARK: - Court Utilization // MARK: - Court Utilization
struct CourtUtilizationView: View { struct CourtUtilizationView: View {
@State var utilization: String
var body: some View { var body: some View {
HStack { HStack {
Text("Court utilization") Text("Court utilization")
.font(.subheadline) .font(.subheadline)
.bold() .bold()
Spacer() Spacer()
Text("66%") Text(utilization)
.font(.subheadline) .font(.subheadline)
.bold() .bold()
} }
@@ -18,7 +18,7 @@ struct FilterScreen: View {
@State private var selectedDuration: String = "This Day" @State private var selectedDuration: String = "This Day"
let durationOptions = ["This Day", "This Week", "This Month", "This Year"] let durationOptions = ["This Day", "This Week", "This Month", "This Year"]
@State var filterBlock : ((String, Int) ->Void)? @State var filterBlock : ((String, String, String, String, Int) ->Void)?
var body: some View { var body: some View {
NavigationView { NavigationView {
@@ -89,7 +89,7 @@ struct FilterScreen: View {
print("Generated Filter Query: \(query)") print("Generated Filter Query: \(query)")
print("Filter count: \(filterCount)") print("Filter count: \(filterCount)")
self.filterBlock?(query, filterCount) self.filterBlock?(startDateStr, endDateStr, startTimeStr, endTimeStr, filterCount)
self.navigationPaths.removeLast() self.navigationPaths.removeLast()
} }
} }
@@ -16,7 +16,8 @@ struct SigninView: View {
@State private var keepLoggedIn = false @State private var keepLoggedIn = false
@State private var showPassword = false @State private var showPassword = false
@EnvironmentObject var viewModel: AuthViewModel @EnvironmentObject var viewModel: AuthViewModel
@EnvironmentObject var dashViewModel : DashViewModel
var body: some View { var body: some View {
NavigationStack(path: $navigationPaths) { NavigationStack(path: $navigationPaths) {
ScrollView { ScrollView {
@@ -106,6 +107,7 @@ struct SigninView: View {
Button(action: { Button(action: {
viewModel.login {_ in viewModel.login {_ in
// Navigate to the Dashboard on successful login // Navigate to the Dashboard on successful login
dashViewModel.getProfile()
navigationPaths.removeAll() navigationPaths.removeAll()
navigationPaths.append("Dashboard") navigationPaths.append("Dashboard")
} }
@@ -138,12 +138,12 @@ struct SideMenuView: View {
} }
Text("John Smith") Text("\(AppSettings.fName) \(AppSettings.lastName)")
.padding(.leading) .padding(.leading)
.font(.system(size: 18, weight: .bold)) .font(.system(size: 18, weight: .bold))
.foregroundColor(.black) .foregroundColor(.black)
Text("John@gmail.com") Text(AppSettings.email)
.padding(.leading) .padding(.leading)
.font(.system(size: 14, weight: .semibold)) .font(.system(size: 14, weight: .semibold))
.foregroundColor(.gray) .foregroundColor(.gray)
@@ -353,3 +353,18 @@ extension String {
return formatter.string(from: date) return formatter.string(from: date)
} }
} }
extension Date {
var toCustomString: String {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MMM-yyyy"
return formatter.string(from: self)
}
}
extension Date {
var toTimeString: String {
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm a"
return formatter.string(from: self)
}
}
@@ -71,7 +71,8 @@ class AuthViewModel: ObservableObject {
// Save the token and user ID for the session // Save the token and user ID for the session
AppSettings.token = response.token AppSettings.token = response.token
AppSettings.userID = response.userId AppSettings.userID = response.userId
AppSettings.lastName
// Update login state // Update login state
self.isLoggedIn = true self.isLoggedIn = true
completion(true) // Proceed to Dashboard completion(true) // Proceed to Dashboard
@@ -18,13 +18,21 @@ class DashViewModel: ObservableObject {
@Published var clubDetail : ClubDetailsResponse? @Published var clubDetail : ClubDetailsResponse?
@Published var profile : ProfileDetailResponse? @Published var profile : ProfileDetailResponse?
@Published var sessionExpired = false @Published var sessionExpired = false
@Published var selectedReservation : ReservationList?
@Published var players : [UserMap]?
@Published var coaches : [PlayerList]?
func getStats(completion: @escaping () -> ()) { func getStats(
startDate: String? = "2024-10-25",
endDate: String? = "2024-11-25",
startTime: String? = "17:30:20",
endTime: String? = "19:20:30",
completion: @escaping () -> ()) {
let endpoint = ClubStatisticsEndpoint( let endpoint = ClubStatisticsEndpoint(
startDate: "2024-10-25", startDate: startDate!,
endDate: "2024-11-25", endDate: endDate!,
startTime: "17:30:20", startTime: startTime!,
endTime: "19:20:30" endTime: endTime!
) )
Task { Task {
@@ -60,8 +68,19 @@ class DashViewModel: ObservableObject {
} }
} }
func getAvailability(){ func getAvailability(
let endpoint = AvailabilityListEndpoint() startDate: String? = "2024-10-25",
endDate: String? = "2024-11-25",
startTime: String? = "17:30:20",
endTime: String? = "19:20:30"
){
let endpoint = AvailabilityListEndpoint(
startDate: startDate!,
endDate: endDate!,
startTime: startTime!,
endTime: endTime!
)
Task { Task {
do { do {
let result = try await APIService.shared.request(endpoint, responseType: AvailabliltyRsp.self) let result = try await APIService.shared.request(endpoint, responseType: AvailabliltyRsp.self)
@@ -82,7 +101,11 @@ class DashViewModel: ObservableObject {
profile = result profile = result
AppSettings.clubId = result?.model?.club?.id ?? 0 AppSettings.clubId = result?.model?.club?.id ?? 0
AppSettings.clubName = result?.model?.club?.name ?? "" AppSettings.clubName = result?.model?.club?.name ?? ""
AppSettings.clubName = result?.model?.user?.email ?? "" AppSettings.email = result?.model?.user?.email ?? ""
AppSettings.photo = result?.model?.user?.photo ?? ""
AppSettings.fName = result?.model?.user?.first_name ?? ""
AppSettings.lastName = result?.model?.user?.last_name ?? ""
AppSettings.clubLogo = result?.model?.club?.club_logo ?? ""
} catch { } catch {
print("Error fetching reservations:", error) print("Error fetching reservations:", error)
@@ -101,4 +124,29 @@ class DashViewModel: ObservableObject {
} }
} }
} }
func getPlayers(playerIds: String){
let endpoint = GetPlayerListEndpoint(playerIds: playerIds)
Task {
do {
let result = try await APIService.shared.requestPlayers(endpoint)
players = result?.list ?? []
} catch {
print("Error fetching reservations:", error)
}
}
}
func getCoaches(playerIds: String){
let endpoint = GetCoachListEndpoint(playerIds: playerIds)
Task {
do {
let result = try await APIService.shared.requestCoach(endpoint)
coaches = result?.list
} catch {
print("Error fetching reservations:", error)
}
}
}
} }