diff --git a/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 7f0b0e8..7eb7a6d 100644 --- a/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/Club_portal/Club Portal/Club Portal.xcodeproj/xcuserdata/umertahir.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -4,22 +4,6 @@ type = "1" version = "2.0"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -260,38 +148,6 @@ landmarkType = "7"> - - - - - - - - - - - - + startingLineNumber = "58" + endingLineNumber = "58" + landmarkName = "body" + landmarkType = "24"> diff --git a/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/Contents.json b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..6623abc 100644 --- a/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "WhatsApp Image 2024-11-29 at 00.34.09 1.jpeg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -12,6 +13,7 @@ "value" : "dark" } ], + "filename" : "WhatsApp Image 2024-11-29 at 00.34.09 2.jpeg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -23,6 +25,7 @@ "value" : "tinted" } ], + "filename" : "WhatsApp Image 2024-11-29 at 00.34.09.jpeg", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09 1.jpeg b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09 1.jpeg new file mode 100644 index 0000000..6dd7c01 Binary files /dev/null and b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09 1.jpeg differ diff --git a/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09 2.jpeg b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09 2.jpeg new file mode 100644 index 0000000..6dd7c01 Binary files /dev/null and b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09 2.jpeg differ diff --git a/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09.jpeg b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09.jpeg new file mode 100644 index 0000000..6dd7c01 Binary files /dev/null and b/Club_portal/Club Portal/Club Portal/Assets.xcassets/AppIcon.appiconset/WhatsApp Image 2024-11-29 at 00.34.09.jpeg differ diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/APIError.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/APIError.swift index 823afa4..55cde16 100644 --- a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/APIError.swift +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/APIError.swift @@ -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(_ endpoint: Endpoint, responseType: T.Type) async throws -> T { // guard let urlRequest = endpoint.urlRequest else { @@ -235,7 +327,7 @@ final class APIService { // } else { // print("Failed to convert response data to string") // } -// +// // } else { // throw APIError.invalidResponse(response.response?.statusCode ?? 0, error.localizedDescription) // } diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/AvailabilityListEndpoint.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/AvailabilityListEndpoint.swift index 435dfdf..86139b8 100644 --- a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/AvailabilityListEndpoint.swift +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/AvailabilityListEndpoint.swift @@ -8,7 +8,6 @@ import SwiftUI struct AvailabilityListEndpoint: Endpoint { - var urlQueryItems: [URLQueryItem] = [] var path: String { "/v3/api/custom/courtmatchup/club/reservations/availability" @@ -16,7 +15,19 @@ struct AvailabilityListEndpoint: Endpoint { 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 } @@ -24,3 +35,51 @@ struct AvailabilityListEndpoint: Endpoint { 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 } +} diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/ReservationListEndpoint.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/ReservationListEndpoint.swift index 1a22caa..a948cdd 100644 --- a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/ReservationListEndpoint.swift +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/EndPoints/ReservationListEndpoint.swift @@ -9,7 +9,7 @@ import SwiftUI struct ReservationListEndpoint: Endpoint { var urlQueryItems: [URLQueryItem] = [] 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 } diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/AvailabliltyRsp.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/AvailabliltyRsp.swift index 8d435ad..d3ec5ce 100644 --- a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/AvailabliltyRsp.swift +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/AvailabliltyRsp.swift @@ -31,7 +31,7 @@ struct Coaches: Codable { // MARK: - CoachesAvailable struct CoachesAvailable: Codable , Identifiable, Hashable { - let id: Int? + let id = UUID().uuidString let coachID, userID: Int? let bio, hourlyRate, availability, firstName: String? let lastName, phone, email: String? diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/Model/CoacheList.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/Model/CoacheList.swift new file mode 100644 index 0000000..a3b7d97 --- /dev/null +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/Model/CoacheList.swift @@ -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"] + } + +} + + + diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/Model/Players.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/Model/Players.swift new file mode 100644 index 0000000..3d8faa6 --- /dev/null +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/Model/Players.swift @@ -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"] + } + +} diff --git a/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityCardView.swift b/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityCardView.swift index a9db5ba..39e88cc 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityCardView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityCardView.swift @@ -51,16 +51,16 @@ struct AvailabilityCardView: View { Divider() } case .coaches: - ForEach(coaches ?? [], id: \.self) { coach in + ForEach(self.coaches ?? [], id: \.id) { coach in HStack { ProfileImageView(imageUrl: coach.photo ?? "", size: 30) - Text(coach.firstName ?? "") + Text("\(coach.firstName ?? "") \(coach.lastName ?? "") ") Spacer() Image("send") .foregroundColor(.gray) .onTapGesture { - name = coach.firstName ?? "" + name = "\(coach.firstName ?? "") \(coach.lastName ?? "") " email = coach.email ?? "" phone = coach.phone ?? "" showSheet.toggle() @@ -70,10 +70,10 @@ struct AvailabilityCardView: View { Divider() } case .staff: - ForEach(self.stsff ?? [], id: \.self) { coach in + ForEach(self.stsff ?? [], id: \.id) { coach in HStack { ProfileImageView(imageUrl: coach.photo ?? "", size: 30) - Text( coach.firstName ?? "") + Text("\(coach.firstName ?? "") \(coach.lastName ?? "") ") Spacer() Image("send") .foregroundColor(.gray) diff --git a/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityScreen.swift b/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityScreen.swift index 50766cb..6db8c29 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityScreen.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityScreen.swift @@ -13,13 +13,16 @@ struct AvailabilityScreen: View { @Binding var presentSideMenu: Bool // Optional for side menu @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 { VStack(spacing: 16) { HeaderView(presentSideMenu: $presentSideMenu) - DateSelectorView() + DateSelectorView(startDate: $startDate, endDate: $endDate, startTime: $startTime, endTime: $endTime) TabSelectorView(selectedTab: $selectedTab) ScrollView { @@ -36,7 +39,14 @@ struct AvailabilityScreen: View { } .background(Color(.systemGray6).ignoresSafeArea()) .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) + } + } } diff --git a/Club_portal/Club Portal/Club Portal/UI/Availbility/DateSelectorView.swift b/Club_portal/Club Portal/Club Portal/UI/Availbility/DateSelectorView.swift index 22b54ea..dc8f9b5 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Availbility/DateSelectorView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Availbility/DateSelectorView.swift @@ -9,11 +9,11 @@ import SwiftUI struct DateSelectorView: View { - @State private var startDate: Date = Date() - @State private var endDate: Date = Calendar.current.date(byAdding: .day, value: 1, to: Date()) ?? Date() + @Binding var startDate: Date + @Binding var endDate: Date - @State private var startTime: Date = Date() - @State private var endTime: Date = Calendar.current.date(byAdding: .hour, value: 1, to: Date()) ?? Date() + @Binding var startTime: Date + @Binding var endTime: Date var body: some View { VStack(spacing: 12) { diff --git a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/DetailsTab.swift b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/DetailsTab.swift index 2c72330..caa956b 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/DetailsTab.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/DetailsTab.swift @@ -25,19 +25,21 @@ struct DetailRow: View { } struct DetailsTab: View { + @EnvironmentObject var viewModel : DashViewModel + var body: some View { ScrollView { VStack(alignment: .leading, spacing: 16) { Group { - DetailRow(title: "Reservation date", value: "16 April, 2024") - DetailRow(title: "Time", value: "9:00 AM – 10:00 AM") - DetailRow(title: "Event type", value: "Lesson") - DetailRow(title: "Sport/type", value: "Tennis • Indoors • Clay") + DetailRow(title: "Reservation date", value: viewModel.selectedReservation?.booking?.date ?? "16 April, 2024") + 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: "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: "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: "Note", value: "{content.note}") + DetailRow(title: "Note", value: "\(viewModel.selectedReservation?.booking?.notes ?? "--")") } } .padding() diff --git a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/PlayersTab.swift b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/PlayersTab.swift index c180c3d..8abaaea 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/PlayersTab.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/PlayersTab.swift @@ -8,6 +8,8 @@ import SwiftUI struct PlayersTab: View { + @EnvironmentObject var viewModel : DashViewModel + var body: some View { ScrollView { VStack(alignment: .leading, spacing: 12) { diff --git a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/ScheduleView.swift b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/ScheduleView.swift index 3e89262..18f19a6 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/ScheduleView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/ScheduleView.swift @@ -9,7 +9,7 @@ struct ScheduleView: View { // @State private var events: [ReservationList] = [] @Binding var presentSideMenu: Bool // Optional for side menu @State var courtName = "" - let hours = Array(4...20) + let hours = Array(0...23) let calendar = Calendar.current @EnvironmentObject var viewModel : DashViewModel @@ -48,11 +48,49 @@ struct ScheduleView: View { } // Time Slots with Events ScrollView { - VStack(spacing: 0) { - ForEach(hours, id: \.self) { hour in - timeSlotRow(hour: hour) - + ZStack(alignment: .topLeading) { + VStack(spacing: 0) { + 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(.bottom, 20) @@ -202,14 +240,13 @@ struct ScheduleView: View { HStack(alignment: .top) { Text(timeLabel(for: hour)) .font(.caption) - .frame(width: 50, alignment: .leading) + .frame(width: 55, alignment: .leading) .padding(.top) ZStack(alignment: .topLeading) { Rectangle() .fill(Color(.systemGray6)) .frame(height: 60) - eventOverlay(for: hour) } } @@ -242,23 +279,36 @@ struct ScheduleView: View { }), id: \.id ) { event in - VStack(alignment: .leading, spacing: 2) { - Text(event.booking?.type ?? "- -") - .font(.subheadline) - .bold() - Text("\(event.booking!.start_time!.to12HourFormat()) - \(event.booking!.end_time!.to12HourFormat())") - .font(.caption) - .foregroundColor(.gray) - } - .padding(8) - .background(Colorr.greenColor.opacity(0.2)) - .foregroundColor(Colorr.greenColor.opacity(0.8)) - .cornerRadius(12) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .background(Color.clear) - .onTapGesture { - self.navigationPaths.append("ReservationDetailsView") - } + let height: CGFloat = { + if let start = DateFormatter.timeOnlyFormatter.date(from: event.booking!.start_time!), + let end = DateFormatter.timeOnlyFormatter.date(from: event.booking!.end_time!) { + let startHour = calendar.component(.hour, from: start) + let endHour = calendar.component(.hour, from: end) + let duration = max(endHour - startHour, 1) + return CGFloat(duration) * 60 + } + return 60 + }() + Color.clear + .overlay( + VStack(alignment: .leading, spacing: 2) { + Text(event.booking?.type ?? "- -") + .font(.subheadline) + .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) @@ -345,6 +395,14 @@ extension DateFormatter { } } +extension DateFormatter { + static var timeOnlyFormatter: DateFormatter { + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + return formatter + } +} + extension String { func to12HourFormat() -> String { let inputFormatter = DateFormatter() diff --git a/Club_portal/Club Portal/Club Portal/UI/Dashboard/AnalyticsView.swift b/Club_portal/Club Portal/Club Portal/UI/Dashboard/AnalyticsView.swift index 031f095..f73a4d1 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Dashboard/AnalyticsView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Dashboard/AnalyticsView.swift @@ -19,9 +19,9 @@ struct AnalyticsView: View { // SegmentedControlView() ScrollView { VStack(spacing: 15) { -// PieChartCard(data: self.viewModel.statResponse?.revenueByModule ?? []) + PieChartCard(data: self.viewModel.statResponse?.revenueByModule ?? []) // BarChartCard(data: self.viewModel.statResponse?.revenueHeatMap ?? []) - PieChartCard1() + // PieChartCard1() BarChartCard1() } .padding(.horizontal) diff --git a/Club_portal/Club Portal/Club Portal/UI/Dashboard/DashboardView.swift b/Club_portal/Club Portal/Club Portal/UI/Dashboard/DashboardView.swift index d1c3876..46c3e16 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Dashboard/DashboardView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Dashboard/DashboardView.swift @@ -15,6 +15,7 @@ struct SummaryView: View { @EnvironmentObject var viewModel : DashViewModel @Binding var presentSideMenu: Bool // Optional for side menu @State var filterCount = 0 + var body: some View { NavigationStack(path: $navigationPaths) { VStack { @@ -26,7 +27,7 @@ struct SummaryView: View { if selectedTab == 0 { FilterView(navigationPaths: $navigationPaths, filterCount: $filterCount) - CourtUtilizationView() + CourtUtilizationView(utilization: "\(self.viewModel.statResponse?.courtUtilization.first?.utilization_percentage ?? 10.0)%" ) ScrollView { LazyVStack(spacing: 10) { @@ -54,9 +55,9 @@ struct SummaryView: View { .navigationBarHidden(true) .navigationDestination(for: String.self) { value in if value == "FilterScreen" { - FilterScreen(navigationPaths: $navigationPaths) { filter, filterCount in + FilterScreen(navigationPaths: $navigationPaths) { startDate, endDate, startTime, endTime, filterCount in 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.getProfile() + // viewModel.getProfile() } @@ -83,16 +84,14 @@ struct HeaderView: View { var body: some View { HStack { - Image(systemName: "person.circle.fill") - .resizable() - .frame(width: 32, height: 32) - .onTapGesture { + ProfileImageView(imageUrl: AppSettings.clubLogo, size: 32) + .onTapGesture { presentSideMenu.toggle() } Spacer() - Text("The Royal Club") + Text(AppSettings.clubName) .font(.headline) Spacer() @@ -199,13 +198,14 @@ struct FilterView: View { // MARK: - Court Utilization struct CourtUtilizationView: View { + @State var utilization: String var body: some View { HStack { Text("Court utilization") .font(.subheadline) .bold() Spacer() - Text("66%") + Text(utilization) .font(.subheadline) .bold() } diff --git a/Club_portal/Club Portal/Club Portal/UI/Dashboard/FilterScreen.swift b/Club_portal/Club Portal/Club Portal/UI/Dashboard/FilterScreen.swift index 52fb97b..4432e64 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Dashboard/FilterScreen.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Dashboard/FilterScreen.swift @@ -18,7 +18,7 @@ struct FilterScreen: View { @State private var selectedDuration: String = "This Day" 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 { NavigationView { @@ -89,7 +89,7 @@ struct FilterScreen: View { print("Generated Filter Query: \(query)") print("Filter count: \(filterCount)") - self.filterBlock?(query, filterCount) + self.filterBlock?(startDateStr, endDateStr, startTimeStr, endTimeStr, filterCount) self.navigationPaths.removeLast() } } diff --git a/Club_portal/Club Portal/Club Portal/UI/OnBoarding/SigninView.swift b/Club_portal/Club Portal/Club Portal/UI/OnBoarding/SigninView.swift index 932a4db..9dfa617 100644 --- a/Club_portal/Club Portal/Club Portal/UI/OnBoarding/SigninView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/OnBoarding/SigninView.swift @@ -16,7 +16,8 @@ struct SigninView: View { @State private var keepLoggedIn = false @State private var showPassword = false @EnvironmentObject var viewModel: AuthViewModel - + @EnvironmentObject var dashViewModel : DashViewModel + var body: some View { NavigationStack(path: $navigationPaths) { ScrollView { @@ -106,6 +107,7 @@ struct SigninView: View { Button(action: { viewModel.login {_ in // Navigate to the Dashboard on successful login + dashViewModel.getProfile() navigationPaths.removeAll() navigationPaths.append("Dashboard") } diff --git a/Club_portal/Club Portal/Club Portal/UI/SideMenu/SideMenuView.swift b/Club_portal/Club Portal/Club Portal/UI/SideMenu/SideMenuView.swift index 51d8e77..5e0300a 100644 --- a/Club_portal/Club Portal/Club Portal/UI/SideMenu/SideMenuView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/SideMenu/SideMenuView.swift @@ -138,12 +138,12 @@ struct SideMenuView: View { } - Text("John Smith") + Text("\(AppSettings.fName) \(AppSettings.lastName)") .padding(.leading) .font(.system(size: 18, weight: .bold)) .foregroundColor(.black) - Text("John@gmail.com") + Text(AppSettings.email) .padding(.leading) .font(.system(size: 14, weight: .semibold)) .foregroundColor(.gray) diff --git a/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift b/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift index 9a89438..6c139fe 100644 --- a/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift +++ b/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift @@ -353,3 +353,18 @@ extension String { 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) + } +} diff --git a/Club_portal/Club Portal/Club Portal/ViewModels/AuthViewModel1.swift b/Club_portal/Club Portal/Club Portal/ViewModels/AuthViewModel1.swift index ec53ffb..f640464 100644 --- a/Club_portal/Club Portal/Club Portal/ViewModels/AuthViewModel1.swift +++ b/Club_portal/Club Portal/Club Portal/ViewModels/AuthViewModel1.swift @@ -71,7 +71,8 @@ class AuthViewModel: ObservableObject { // Save the token and user ID for the session AppSettings.token = response.token AppSettings.userID = response.userId - AppSettings.lastName + + // Update login state self.isLoggedIn = true completion(true) // Proceed to Dashboard diff --git a/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift b/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift index 73e63f3..2292948 100644 --- a/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift +++ b/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift @@ -18,13 +18,21 @@ class DashViewModel: ObservableObject { @Published var clubDetail : ClubDetailsResponse? @Published var profile : ProfileDetailResponse? @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( - startDate: "2024-10-25", - endDate: "2024-11-25", - startTime: "17:30:20", - endTime: "19:20:30" + startDate: startDate!, + endDate: endDate!, + startTime: startTime!, + endTime: endTime! ) Task { @@ -60,8 +68,19 @@ class DashViewModel: ObservableObject { } } - func getAvailability(){ - let endpoint = AvailabilityListEndpoint() + func getAvailability( + 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 { do { let result = try await APIService.shared.request(endpoint, responseType: AvailabliltyRsp.self) @@ -82,7 +101,11 @@ class DashViewModel: ObservableObject { profile = result AppSettings.clubId = result?.model?.club?.id ?? 0 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 { 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) + } + } + } }