From 154d1882c89dd31ad055c17b2a20eb898274abc2 Mon Sep 17 00:00:00 2001 From: umer Date: Tue, 20 May 2025 02:48:06 +0500 Subject: [PATCH] fixed some issues --- .../Club Portal.xcodeproj/project.pbxproj | 4 +- .../xcdebugger/Breakpoints_v2.xcbkptlist | 16 - .../Network/Api_Stuff/APIError.swift | 46 +++ .../Response/ClubStatisticsResponse.swift | 333 +++++++++++++++--- .../Response/ReservationResponse.swift | 3 + .../UI/Availbility/AvailabilityScreen.swift | 6 + .../UI/DailySecheduler/CoachesTab.swift | 46 ++- .../UI/DailySecheduler/DetailsTab.swift | 2 +- .../UI/DailySecheduler/FilterView1.swift | 2 +- .../UI/DailySecheduler/PlayersTab.swift | 54 +-- .../UI/DailySecheduler/ScheduleView.swift | 29 +- .../UI/Dashboard/AnalyticsView.swift | 25 +- .../UI/Dashboard/DashboardView.swift | 54 ++- .../UI/Dashboard/FilterScreen.swift | 43 +-- .../UI/SideMenu/SideMenuView.swift | 1 + .../Club Portal/Utliz/Extensions.swift | 10 + .../ViewModels/DashViewModel.swift | 29 +- 17 files changed, 550 insertions(+), 153 deletions(-) diff --git a/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj b/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj index 27240df..54b38b2 100644 --- a/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj +++ b/Club_portal/Club Portal/Club Portal.xcodeproj/project.pbxproj @@ -267,7 +267,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\""; DEVELOPMENT_TEAM = BSGYUG5U29; ENABLE_PREVIEWS = YES; @@ -300,7 +300,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\""; DEVELOPMENT_TEAM = BSGYUG5U29; ENABLE_PREVIEWS = YES; 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 7eb7a6d..39e3c49 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 @@ -100,22 +100,6 @@ landmarkType = "24"> - - - - ClubStatisticsResponse? { + 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 = ClubStatisticsResponse(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 diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ClubStatisticsResponse.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ClubStatisticsResponse.swift index fcd15cd..e6585a4 100644 --- a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ClubStatisticsResponse.swift +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ClubStatisticsResponse.swift @@ -4,65 +4,314 @@ // // Created by Umer Tahir on 11/04/2025. // +import ObjectMapper -struct ClubStatisticsResponse: Decodable { - let error: Bool - let model: ClubStatisticsModel +class ClubStatisticsResponse : Mappable { + var error : Bool? + var model : ClubStatisticsModel? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + error <- map["error"] + model <- map["model"] + } + } -struct ClubStatisticsModel: Decodable { - let clinicReservations: [ReservationStats] - let coachReservations: [ReservationStats] - let totalReservations: [ReservationStats] - let revenueHeatMap: [RevenueHeatMap] - let revenueByDateRange: [RevenueByDateRange] - let revenueByModule: [RevenueByModule] - let buddyStatistics: [ReservationStats] - let totalExpenses: [ExpenseStats] - let totalRevenue: [RevenueStats] - let totalProfit: [ProfitStats] - let courtUtilization: [CourtUtilizationStats] - let lessonReservations: [ReservationStats] +class ClubStatisticsModel : Mappable { + var clinicReservations : [ClinicReservations]? + var coachReservations : [CoachReservations]? + var totalReservations : [TotalReservations]? + var revenueHeatMap : [RevenueHeatMap]? + var revenueByDateRange : [RevenueByDateRange]? + var revenueByModule : [RevenueByModule]? + var buddyStatistics : [BuddyStatistics]? + var totalExpenses : [TotalExpenses]? + var totalRevenue : [TotalRevenue]? + var totalProfit : [TotalProfit]? + var courtUtilization : [CourtUtilization]? + var lessonReservations : [LessonReservations]? + var revenueByDay : [RevenueByDay]? + var revenueBarChart : RevenueBarChart? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + clinicReservations <- map["clinicReservations"] + coachReservations <- map["coachReservations"] + totalReservations <- map["totalReservations"] + revenueHeatMap <- map["revenueHeatMap"] + revenueByDateRange <- map["revenueByDateRange"] + revenueByModule <- map["revenueByModule"] + buddyStatistics <- map["buddyStatistics"] + totalExpenses <- map["totalExpenses"] + totalRevenue <- map["totalRevenue"] + totalProfit <- map["totalProfit"] + courtUtilization <- map["courtUtilization"] + lessonReservations <- map["lessonReservations"] + revenueByDay <- map["revenueByDay"] + revenueBarChart <- map["revenueBarChart"] + } + } -struct ReservationStats: Decodable { - let total_hours: Double? - let total_revenue: Double? +class BuddyStatistics : Mappable { + var total_hours : String? + var total_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_revenue <- map["total_revenue"] + } + } -struct RevenueHeatMap: Decodable { - let date: String - let total_revenue: Double +class ClinicReservations : Mappable { + var total_hours : String? + var total_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_revenue <- map["total_revenue"] + } + } -struct RevenueByDateRange: Decodable { - // Add fields if needed + + +class CoachReservations : Mappable { + var total_hours : String? + var total_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_revenue <- map["total_revenue"] + } + } -struct RevenueByModule: Decodable { - let module: String - let revenue: Double? +class CourtUtilization : Mappable { + var utilization_percentage : String? + var total_used_hours : String? + var total_available_hours : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + utilization_percentage <- map["utilization_percentage"] + total_used_hours <- map["total_used_hours"] + total_available_hours <- map["total_available_hours"] + } + } -struct ExpenseStats: Decodable { - let total_hours: Double? - let total_expense: Double? +class BarData : Mappable { + var clinic : [Int]? + var coach : [Double]? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + clinic <- map["clinic"] + coach <- map["coach"] + } + } -struct RevenueStats: Decodable { - let total_hours: Double? - let total_revenue: Double? +class LessonReservations : Mappable { + var total_hours : String? + var total_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_revenue <- map["total_revenue"] + } + } -struct ProfitStats: Decodable { - let total_profit: Double? - let total_revenue: Double? - let total_expense: Double? + +class RevenueBarChart : Mappable { + var days : [String]? + var data : BarData? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + days <- map["days"] + data <- map["data"] + } + } -struct CourtUtilizationStats: Decodable { - let utilization_percentage: Double? - let total_used_hours: Double? - let total_available_hours: Double? -} \ No newline at end of file +class RevenueByDateRange : Mappable { + var date : String? + var clinic_revenue : String? + var coach_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + date <- map["date"] + clinic_revenue <- map["clinic_revenue"] + coach_revenue <- map["coach_revenue"] + } + +} + +class RevenueByDay : Mappable { + var day : String? + var module : String? + var revenue : Double? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + day <- map["day"] + module <- map["module"] + revenue <- map["revenue"] + } + +} + + +class RevenueByModule : Mappable { + var module : String! + var revenue : Int! + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + module <- map["module"] + revenue <- map["revenue"] + } + +} + +class RevenueHeatMap : Mappable { + var date : String? + var total_revenue : Double? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + date <- map["date"] + total_revenue <- map["total_revenue"] + } + +} + +class TotalExpenses : Mappable { + var total_hours : String? + var total_expense : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_expense <- map["total_expense"] + } + +} + +class TotalProfit : Mappable { + var total_profit : Int? + var total_revenue : Double? + var total_expense : Double? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_profit <- map["total_profit"] + total_revenue <- map["total_revenue"] + total_expense <- map["total_expense"] + } + +} + +class TotalReservations : Mappable { + var total_hours : String? + var total_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_revenue <- map["total_revenue"] + } + +} + + +class TotalRevenue : Mappable { + var total_hours : String? + var total_revenue : String? + + required init?(map: Map) { + + } + + func mapping(map: Map) { + + total_hours <- map["total_hours"] + total_revenue <- map["total_revenue"] + } + +} diff --git a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ReservationResponse.swift b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ReservationResponse.swift index 13ef3a5..c743e1d 100644 --- a/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ReservationResponse.swift +++ b/Club_portal/Club Portal/Club Portal/Network/Api_Stuff/Response/ReservationResponse.swift @@ -101,6 +101,7 @@ class Booking : Mappable, Identifiable { var create_at : String? var update_at : String? var coach_ids : String? + var sport_name : String? required init?(map: Map) { @@ -144,6 +145,8 @@ class Booking : Mappable, Identifiable { create_at <- map["create_at"] update_at <- map["update_at"] coach_ids <- map["coach_ids"] + sport_name <- map["sport_name"] + } } 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 6db8c29..4ad21b3 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityScreen.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Availbility/AvailabilityScreen.swift @@ -47,6 +47,12 @@ struct AvailabilityScreen: View { .onChange(of: endDate) { _ in viewModel.getAvailability(startDate: startDate.toCustomString, endDate: endDate.toCustomString, startTime: startTime.toTimeString, endTime: endTime.toTimeString) } + .onChange(of: startTime) { _ in + viewModel.getAvailability(startDate: startDate.toCustomString, endDate: endDate.toCustomString, startTime: startTime.toTimeString, endTime: endTime.toTimeString) + } + .onChange(of: endTime) { _ 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/DailySecheduler/CoachesTab.swift b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/CoachesTab.swift index a2af383..5831a5f 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/CoachesTab.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/CoachesTab.swift @@ -8,29 +8,47 @@ import SwiftUI struct CoachesTab: View { + @EnvironmentObject var viewModel : DashViewModel + var body: some View { ScrollView { VStack(alignment: .leading, spacing: 12) { - ForEach(0..<3, id: \.self) { _ in - HStack(spacing: 12) { - ProfileImageView(imageUrl: "", size: 40) - - VStack(alignment: .leading, spacing: 4) { - Text("Steve Jobs") - .font(.headline) - Text("Hours playing: 9:00 AM – 10:00 AM") - .font(.caption) - .foregroundColor(.secondary) + + if let players = viewModel.coaches { + + + ForEach(players, id: \.id) { index in + HStack(spacing: 12) { + ProfileImageView(imageUrl: index.user?.photo ?? "", size: 40) + + VStack(alignment: .leading, spacing: 2) { + Text("\(index.user?.first_name ?? "") \(index.user?.last_name ?? "" )") + .font(.headline) + + + + } + + Spacer() } - - Spacer() + + } + + .padding() - .background(Color(.systemGray6)) - .cornerRadius(10) + + + } } .padding() + .onAppear(){ + if let ids = viewModel.selectedReservation?.booking?.coach_ids?.toCommaSeparated { + + viewModel.getCoaches(playerIds: ids) + } + } } } } 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 caa956b..950356c 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/DetailsTab.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/DetailsTab.swift @@ -34,7 +34,7 @@ struct DetailsTab: View { 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: "Sport/type", value: "\(viewModel.selectedReservation?.booking?.sport_name ?? "") • \(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: "$\(viewModel.selectedReservation?.booking?.court_fee ?? "10")") diff --git a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/FilterView1.swift b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/FilterView1.swift index 4856164..c4a0e95 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/FilterView1.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/FilterView1.swift @@ -43,7 +43,7 @@ struct ScheduleFilterView: View { } .padding(.top, 10) } label: { - Text("Club") + Text("Courts") .font(.headline) } .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 8abaaea..16ff202 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/PlayersTab.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/PlayersTab.swift @@ -13,34 +13,44 @@ struct PlayersTab: View { var body: some View { ScrollView { VStack(alignment: .leading, spacing: 12) { - ForEach(0..<5, id: \.self) { index in - HStack(spacing: 12) { - ProfileImageView(imageUrl: "", size: 40) - - VStack(alignment: .leading, spacing: 2) { - Text("Steve Jobs") - .font(.headline) - - if index == 0 { - Text("Guardian sign-in: Steve Jobs") - .font(.caption) - .foregroundColor(.gray) + if let players = viewModel.players { + ForEach(players, id: \.id) { index in + HStack(spacing: 12) { + ProfileImageView(imageUrl: index.photo, size: 40) + + VStack(alignment: .leading, spacing: 2) { + Text("\(index.first_name ?? "") \(index.last_name ?? "" )") + .font(.headline) + +// if index == 0 { +// Text("Guardian sign-in: Steve Jobs") +// .font(.caption) +// .foregroundColor(.gray) +// } } + + Spacer() + +// if index == 0 { +// Image(systemName: "checkmark.seal.fill") +// .foregroundColor(.green) +// } } - - Spacer() - - if index == 0 { - Image(systemName: "checkmark.seal.fill") - .foregroundColor(.green) - } + .padding() + .background(Color(.systemGray6)) + .cornerRadius(10) } - .padding() - .background(Color(.systemGray6)) - .cornerRadius(10) } } .padding() + .onAppear(){ + if let ids = viewModel.selectedReservation?.booking?.player_ids?.toCommaSeparated { + + viewModel.getPlayers(playerIds: ids) + } + } } } } + + 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 18f19a6..f235e92 100644 --- a/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/ScheduleView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/DailySecheduler/ScheduleView.swift @@ -6,12 +6,15 @@ struct ScheduleView: View { @State var navigationPaths: [String] = [] @State private var selectedDate = Date() @State private var displayedMonth = Date() - // @State private var events: [ReservationList] = [] + // @State private var events: [ReservationList] = [] @Binding var presentSideMenu: Bool // Optional for side menu @State var courtName = "" let hours = Array(0...23) let calendar = Calendar.current + @State private var selectedCourtName: String? = nil + @State private var selectedCourtId: Int? = nil + @EnvironmentObject var viewModel : DashViewModel var body: some View { @@ -43,7 +46,28 @@ struct ScheduleView: View { HStack { Spacer() - Text(courtName) + if let clubs = self.viewModel.profile?.model?.courts { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + ForEach(clubs, id: \.self) { court in + if let clubName = court.name { + Button(action: { + selectedCourtName = clubName + selectedCourtId = court.id + viewModel.getDailySched(clubId: court.id ?? 0) + }) { + Text(clubName) + .padding(5) + .background(selectedCourtName == clubName ? Colorr.themeBlueColor : Color.gray.opacity(0.2)) + .foregroundColor(selectedCourtName == clubName ? .white : .primary) + .cornerRadius(8) + } + } + } + } + .padding(.horizontal) + } + } Spacer() } // Time Slots with Events @@ -221,6 +245,7 @@ struct ScheduleView: View { } .onTapGesture { selectedDate = date + viewModel.getDailySched(clubId: selectedCourtId ?? 0) } .id(calendar.startOfDay(for: date)) } 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 f73a4d1..2c80e70 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Dashboard/AnalyticsView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Dashboard/AnalyticsView.swift @@ -20,9 +20,9 @@ struct AnalyticsView: View { ScrollView { VStack(spacing: 15) { PieChartCard(data: self.viewModel.statResponse?.revenueByModule ?? []) -// BarChartCard(data: self.viewModel.statResponse?.revenueHeatMap ?? []) + BarChartCard(data: self.viewModel.statResponse?.revenueHeatMap ?? []) // PieChartCard1() - BarChartCard1() + // BarChartCard1() } .padding(.horizontal) } @@ -86,14 +86,14 @@ struct PieChartCard: View { var body: some View { let chartData = data.map { - (name: $0.module, value: $0.revenue ?? 0, color: randomColor()) + (name: $0.module, value: $0.revenue ?? 0, color: randomColor(module: $0.module)) } CardView(title: "% made through each module") { VStack { HStack { ForEach(chartData, id: \.name) { item in - LegendView(color: item.color, text: item.name) + LegendView(color: item.color, text: item.name ?? "") } .padding(.top, 5) } @@ -111,9 +111,16 @@ struct PieChartCard: View { } } - func randomColor() -> Color { - let colors: [Color] = [Colorr.pieBlue, Colorr.pieGreen, .orange, .purple, .pink, .mint] - return colors.randomElement() ?? .blue + func randomColor(module: String) -> Color { + switch(module){ + case "Clinics": + return Colorr.pieBlue + case "Coaching": + return Colorr.greenColor + default: + return .orange + + } } } @@ -156,10 +163,10 @@ struct BarChartCard: View { var body: some View { let barData: [ModuleRevenue] = data.map { - ModuleRevenue(day: $0.date.toWeekday()!, module: $0.date.toWeekday()!, value: $0.total_revenue ) + ModuleRevenue(day: $0.date?.toWeekday()! ?? "", module: $0.date?.toWeekday() ?? "", value: $0.total_revenue ?? 0.0 ) } - CardView(title: "Revenue from each module") { + CardView(title: "Revenue from each day") { Chart(barData) { data in BarMark( x: .value("Day", data.day), 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 46c3e16..1dd4f70 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Dashboard/DashboardView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Dashboard/DashboardView.swift @@ -27,20 +27,52 @@ struct SummaryView: View { if selectedTab == 0 { FilterView(navigationPaths: $navigationPaths, filterCount: $filterCount) - CourtUtilizationView(utilization: "\(self.viewModel.statResponse?.courtUtilization.first?.utilization_percentage ?? 10.0)%" ) + CourtUtilizationView(utilization: "\(self.viewModel.statResponse?.courtUtilization?.first?.utilization_percentage ?? "10.0")%" ) ScrollView { LazyVStack(spacing: 10) { - ReservationCard(title: "Court reservations", hours: "\(self.viewModel.statResponse?.coachReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.coachReservations.first?.total_revenue ?? 0 )") - ReservationCard(title: "Clinic reservations", hours: "\(self.viewModel.statResponse?.clinicReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.clinicReservations.first?.total_revenue ?? 0 )") - ReservationCard(title: "Find a Buddy", hours: "\(self.viewModel.statResponse?.buddyStatistics.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.buddyStatistics.first?.total_revenue ?? 0 )") - ReservationCard(title: "Lesson reservation", hours: "\(self.viewModel.statResponse?.lessonReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.lessonReservations.first?.total_revenue ?? 0 )") + ReservationCard( + title: "Court reservations", + hours: String(format: "%.2f", self.viewModel.statResponse?.coachReservations?.first?.total_hours ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.coachReservations?.first?.total_revenue ?? 0) + ) - ReservationCard(title: "Total expenses", hours: "\(self.viewModel.statResponse?.totalExpenses.first?.total_hours ?? 0 )", revenue:"$\(self.viewModel.statResponse?.totalExpenses.first?.total_expense ?? 0 )") - ReservationCard(title: "Total profit", hours: "\(self.viewModel.statResponse?.totalProfit.first?.total_profit ?? 0 )", revenue: "$\(self.viewModel.statResponse?.totalProfit.first?.total_revenue ?? 0 )") - ReservationCard(title: "Total revenue", hours: "\(self.viewModel.statResponse?.totalRevenue.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.totalRevenue.first?.total_revenue ?? 0 )") + ReservationCard( + title: "Clinic reservations", + hours: String(format: "%.2f", self.viewModel.statResponse?.clinicReservations?.first?.total_hours ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.clinicReservations?.first?.total_revenue ?? 0) + ) + ReservationCard( + title: "Find a Buddy", + hours: String(format: "%.2f", self.viewModel.statResponse?.buddyStatistics?.first?.total_hours ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.buddyStatistics?.first?.total_revenue ?? 0) + ) + + ReservationCard( + title: "Lesson reservation", + hours: String(format: "%.2f", self.viewModel.statResponse?.lessonReservations?.first?.total_hours ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.lessonReservations?.first?.total_revenue ?? 0) + ) + + ReservationCard( + title: "Total expenses", + hours: String(format: "%.2f", self.viewModel.statResponse?.totalExpenses?.first?.total_hours ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.totalExpenses?.first?.total_expense ?? 0) + ) + + ReservationCard( + title: "Total profit", + hours: String(format: "%.2f", self.viewModel.statResponse?.totalProfit?.first?.total_profit ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.totalProfit?.first?.total_revenue ?? 0) + ) + + ReservationCard( + title: "Total revenue", + hours: String(format: "%.2f", self.viewModel.statResponse?.totalRevenue?.first?.total_hours ?? 0), + revenue: "$" + String(format: "%.2f", self.viewModel.statResponse?.totalRevenue?.first?.total_revenue ?? 0) + ) } .padding(.horizontal) @@ -57,7 +89,7 @@ struct SummaryView: View { if value == "FilterScreen" { FilterScreen(navigationPaths: $navigationPaths) { startDate, endDate, startTime, endTime, filterCount in self.filterCount = filterCount - viewModel.getStats ( startDate: startDate, endDate: endDate, startTime: startTime, endTime: endTime, completion: {}) + // viewModel.getStats ( startDate: startDate, endDate: endDate, startTime: startTime, endTime: endTime, completion: {}) } } } @@ -109,7 +141,9 @@ struct HeaderView: View { AppSettings.token = "" } - Button("Cancel", role: .cancel) { } + Button("Cancel", role: .cancel) { + + } } } } 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 4432e64..0525c81 100644 --- a/Club_portal/Club Portal/Club Portal/UI/Dashboard/FilterScreen.swift +++ b/Club_portal/Club Portal/Club Portal/UI/Dashboard/FilterScreen.swift @@ -14,7 +14,8 @@ struct FilterScreen: View { @State private var endTime: Date = Date() @State private var startDate: Date = Date() @State private var endDate: Date = Date() - + @EnvironmentObject var viewModel : DashViewModel + @State private var selectedDuration: String = "This Day" let durationOptions = ["This Day", "This Week", "This Month", "This Year"] @@ -33,14 +34,14 @@ struct FilterScreen: View { DatePicker("End Date", selection: $endDate, displayedComponents: .date) } - Section(header: Text("Duration")) { - Picker("Duration", selection: $selectedDuration) { - ForEach(durationOptions, id: \.self) { duration in - Text(duration) - } - } - .pickerStyle(MenuPickerStyle()) - } +// Section(header: Text("Duration")) { +// Picker("Duration", selection: $selectedDuration) { +// ForEach(durationOptions, id: \.self) { duration in +// Text(duration) +// } +// } +// .pickerStyle(MenuPickerStyle()) +// } Section { Button(action: { @@ -66,17 +67,17 @@ struct FilterScreen: View { let timeFormatter = DateFormatter() timeFormatter.dateFormat = "HH:mm:ss" - let startDateStr = dateFormatter.string(from: startDate) - let endDateStr = dateFormatter.string(from: endDate) - let startTimeStr = timeFormatter.string(from: startTime) - let endTimeStr = timeFormatter.string(from: endTime) + viewModel.dashStartDate = dateFormatter.string(from: startDate) + viewModel.dashEndDate = dateFormatter.string(from: endDate) + viewModel.dashStartTime = timeFormatter.string(from: startTime) + viewModel.dashEndTime = timeFormatter.string(from: endTime) - let query = """ - start_date=\(startDateStr)&\ - end_date=\(endDateStr)&\ - start_time=\(startTimeStr)&\ - end_time=\(endTimeStr) - """ +// let query = """ +// start_date=\(startDateStr)&\ +// end_date=\(endDateStr)&\ +// start_time=\(startTimeStr)&\ +// end_time=\(endTimeStr) +// """ // 🔢 Count how many filters are applied var filterCount = 0 @@ -86,10 +87,10 @@ struct FilterScreen: View { if !Calendar.current.isDate(endTime, equalTo: Date(), toGranularity: .minute) { filterCount += 1 } if selectedDuration != "This Day" { filterCount += 1 } - print("Generated Filter Query: \(query)") + // print("Generated Filter Query: \(query)") print("Filter count: \(filterCount)") - self.filterBlock?(startDateStr, endDateStr, startTimeStr, endTimeStr, filterCount) + // self.filterBlock?(startDateStr, endDateStr, startTimeStr, endTimeStr, filterCount) self.navigationPaths.removeLast() } } 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 5e0300a..5b2eeef 100644 --- a/Club_portal/Club Portal/Club Portal/UI/SideMenu/SideMenuView.swift +++ b/Club_portal/Club Portal/Club Portal/UI/SideMenu/SideMenuView.swift @@ -61,6 +61,7 @@ struct SideMenuView: View { AppSettings.token = "" presentSideMenu = false } + }) } diff --git a/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift b/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift index 6c139fe..fbe2c35 100644 --- a/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift +++ b/Club_portal/Club Portal/Club Portal/Utliz/Extensions.swift @@ -368,3 +368,13 @@ extension Date { return formatter.string(from: self) } } +extension String { + var toCommaSeparated: String { + self.trimmingCharacters(in: CharacterSet(charactersIn: "[]")) + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespaces) } + .joined(separator: ",") + } +} + + diff --git a/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift b/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift index 2292948..2f1f3c8 100644 --- a/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift +++ b/Club_portal/Club Portal/Club Portal/ViewModels/DashViewModel.swift @@ -21,26 +21,29 @@ class DashViewModel: ObservableObject { @Published var selectedReservation : ReservationList? @Published var players : [UserMap]? @Published var coaches : [PlayerList]? + + @Published var dashStartDate : String = "2025-04-01" + @Published var dashEndDate : String = "2025-04-30" + @Published var dashStartTime : String = "08:30" + @Published var dashEndTime : String = "17:00" 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: startDate!, - endDate: endDate!, - startTime: startTime!, - endTime: endTime! + startDate: dashStartDate, + endDate: dashEndDate, + startTime: dashStartTime, + endTime: dashEndTime ) Task { do { - let response: ClubStatisticsResponse = try await APIService.shared.request(endpoint, responseType: ClubStatisticsResponse.self) - print("Statistics fetched: \(response.model)") - if !response.error { - self.statResponse = response.model + let result = try await APIService.shared.requestStatics(endpoint) + print("Statistics fetched: \(result?.model)") + + if !result!.error! { + self.statResponse = result?.model } else { @@ -49,7 +52,7 @@ class DashViewModel: ObservableObject { print("Error fetching stats: \(error)") // if error.localizedDescription == "Token expired" { sessionExpired = true - completion() + completion() // } } }