fixed some issues

This commit is contained in:
umer
2025-05-20 02:48:06 +05:00
parent f1d04650b6
commit 154d1882c8
17 changed files with 550 additions and 153 deletions
@@ -267,7 +267,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\"";
DEVELOPMENT_TEAM = BSGYUG5U29; DEVELOPMENT_TEAM = BSGYUG5U29;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -300,7 +300,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 3;
DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\"";
DEVELOPMENT_TEAM = BSGYUG5U29; DEVELOPMENT_TEAM = BSGYUG5U29;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -100,22 +100,6 @@
landmarkType = "24"> landmarkType = "24">
</BreakpointContent> </BreakpointContent>
</BreakpointProxy> </BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "1D973B51-0BF4-4CAE-9D3D-08BB2000E001"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Club Portal/UI/Dashboard/DashboardView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "66"
endingLineNumber = "66"
landmarkName = "body"
landmarkType = "24">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy <BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint"> BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent <BreakpointContent
@@ -256,6 +256,52 @@ final class APIService {
} }
} }
func requestStatics(_ endpoint: Endpoint) async throws -> 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? { func requestCoach(_ endpoint: Endpoint) async throws -> CoacheList? {
guard let request = endpoint.urlRequest else { guard let request = endpoint.urlRequest else {
throw APIError.invalidURL throw APIError.invalidURL
@@ -4,65 +4,314 @@
// //
// Created by Umer Tahir on 11/04/2025. // Created by Umer Tahir on 11/04/2025.
// //
import ObjectMapper
struct ClubStatisticsResponse: Decodable { class ClubStatisticsResponse : Mappable {
let error: Bool var error : Bool?
let model: ClubStatisticsModel var model : ClubStatisticsModel?
required init?(map: Map) {
}
func mapping(map: Map) {
error <- map["error"]
model <- map["model"]
}
} }
struct ClubStatisticsModel: Decodable { class ClubStatisticsModel : Mappable {
let clinicReservations: [ReservationStats] var clinicReservations : [ClinicReservations]?
let coachReservations: [ReservationStats] var coachReservations : [CoachReservations]?
let totalReservations: [ReservationStats] var totalReservations : [TotalReservations]?
let revenueHeatMap: [RevenueHeatMap] var revenueHeatMap : [RevenueHeatMap]?
let revenueByDateRange: [RevenueByDateRange] var revenueByDateRange : [RevenueByDateRange]?
let revenueByModule: [RevenueByModule] var revenueByModule : [RevenueByModule]?
let buddyStatistics: [ReservationStats] var buddyStatistics : [BuddyStatistics]?
let totalExpenses: [ExpenseStats] var totalExpenses : [TotalExpenses]?
let totalRevenue: [RevenueStats] var totalRevenue : [TotalRevenue]?
let totalProfit: [ProfitStats] var totalProfit : [TotalProfit]?
let courtUtilization: [CourtUtilizationStats] var courtUtilization : [CourtUtilization]?
let lessonReservations: [ReservationStats] 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 { class BuddyStatistics : Mappable {
let total_hours: Double? var total_hours : String?
let total_revenue: Double? 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 { class ClinicReservations : Mappable {
let date: String var total_hours : String?
let total_revenue: Double 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 { class CourtUtilization : Mappable {
let module: String var utilization_percentage : String?
let revenue: Double? 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 { class BarData : Mappable {
let total_hours: Double? var clinic : [Int]?
let total_expense: Double? var coach : [Double]?
required init?(map: Map) {
}
func mapping(map: Map) {
clinic <- map["clinic"]
coach <- map["coach"]
}
} }
struct RevenueStats: Decodable { class LessonReservations : Mappable {
let total_hours: Double? var total_hours : String?
let total_revenue: Double? 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? class RevenueBarChart : Mappable {
let total_revenue: Double? var days : [String]?
let total_expense: Double? var data : BarData?
required init?(map: Map) {
}
func mapping(map: Map) {
days <- map["days"]
data <- map["data"]
}
} }
struct CourtUtilizationStats: Decodable { class RevenueByDateRange : Mappable {
let utilization_percentage: Double? var date : String?
let total_used_hours: Double? var clinic_revenue : String?
let total_available_hours: Double? 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"]
}
}
@@ -101,6 +101,7 @@ class Booking : Mappable, Identifiable {
var create_at : String? var create_at : String?
var update_at : String? var update_at : String?
var coach_ids : String? var coach_ids : String?
var sport_name : String?
required init?(map: Map) { required init?(map: Map) {
@@ -144,6 +145,8 @@ class Booking : Mappable, Identifiable {
create_at <- map["create_at"] create_at <- map["create_at"]
update_at <- map["update_at"] update_at <- map["update_at"]
coach_ids <- map["coach_ids"] coach_ids <- map["coach_ids"]
sport_name <- map["sport_name"]
} }
} }
@@ -47,6 +47,12 @@ struct AvailabilityScreen: View {
.onChange(of: endDate) { _ in .onChange(of: endDate) { _ in
viewModel.getAvailability(startDate: startDate.toCustomString, endDate: endDate.toCustomString, startTime: startTime.toTimeString, endTime: endTime.toTimeString) 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)
}
} }
} }
@@ -8,29 +8,47 @@
import SwiftUI import SwiftUI
struct CoachesTab: View { struct CoachesTab: 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) {
ForEach(0..<3, id: \.self) { _ in
HStack(spacing: 12) { if let players = viewModel.coaches {
ProfileImageView(imageUrl: "", size: 40)
VStack(alignment: .leading, spacing: 4) { ForEach(players, id: \.id) { index in
Text("Steve Jobs") HStack(spacing: 12) {
.font(.headline) ProfileImageView(imageUrl: index.user?.photo ?? "", size: 40)
Text("Hours playing: 9:00 AM 10:00 AM")
.font(.caption) VStack(alignment: .leading, spacing: 2) {
.foregroundColor(.secondary) Text("\(index.user?.first_name ?? "") \(index.user?.last_name ?? "" )")
.font(.headline)
}
Spacer()
} }
Spacer()
} }
.padding() .padding()
.background(Color(.systemGray6))
.cornerRadius(10)
} }
} }
.padding() .padding()
.onAppear(){
if let ids = viewModel.selectedReservation?.booking?.coach_ids?.toCommaSeparated {
viewModel.getCoaches(playerIds: ids)
}
}
} }
} }
} }
@@ -34,7 +34,7 @@ struct DetailsTab: View {
DetailRow(title: "Reservation date", value: viewModel.selectedReservation?.booking?.date ?? "16 April, 2024") 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: "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: "\(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: "Repeating", value: "Every week")
DetailRow(title: "Space assigned", value: "Court #5") DetailRow(title: "Space assigned", value: "Court #5")
DetailRow(title: "Court fee", value: "$\(viewModel.selectedReservation?.booking?.court_fee ?? "10")") DetailRow(title: "Court fee", value: "$\(viewModel.selectedReservation?.booking?.court_fee ?? "10")")
@@ -43,7 +43,7 @@ struct ScheduleFilterView: View {
} }
.padding(.top, 10) .padding(.top, 10)
} label: { } label: {
Text("Club") Text("Courts")
.font(.headline) .font(.headline)
} }
.padding() .padding()
@@ -13,34 +13,44 @@ struct PlayersTab: View {
var body: some View { var body: some View {
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: 12) {
ForEach(0..<5, id: \.self) { index in if let players = viewModel.players {
HStack(spacing: 12) { ForEach(players, id: \.id) { index in
ProfileImageView(imageUrl: "", size: 40) HStack(spacing: 12) {
ProfileImageView(imageUrl: index.photo, size: 40)
VStack(alignment: .leading, spacing: 2) {
Text("Steve Jobs") VStack(alignment: .leading, spacing: 2) {
.font(.headline) Text("\(index.first_name ?? "") \(index.last_name ?? "" )")
.font(.headline)
if index == 0 {
Text("Guardian sign-in: Steve Jobs") // if index == 0 {
.font(.caption) // Text("Guardian sign-in: Steve Jobs")
.foregroundColor(.gray) // .font(.caption)
// .foregroundColor(.gray)
// }
} }
Spacer()
// if index == 0 {
// Image(systemName: "checkmark.seal.fill")
// .foregroundColor(.green)
// }
} }
.padding()
Spacer() .background(Color(.systemGray6))
.cornerRadius(10)
if index == 0 {
Image(systemName: "checkmark.seal.fill")
.foregroundColor(.green)
}
} }
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
} }
} }
.padding() .padding()
.onAppear(){
if let ids = viewModel.selectedReservation?.booking?.player_ids?.toCommaSeparated {
viewModel.getPlayers(playerIds: ids)
}
}
} }
} }
} }
@@ -6,12 +6,15 @@ struct ScheduleView: View {
@State var navigationPaths: [String] = [] @State var navigationPaths: [String] = []
@State private var selectedDate = Date() @State private var selectedDate = Date()
@State private var displayedMonth = Date() @State private var displayedMonth = Date()
// @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(0...23) let hours = Array(0...23)
let calendar = Calendar.current let calendar = Calendar.current
@State private var selectedCourtName: String? = nil
@State private var selectedCourtId: Int? = nil
@EnvironmentObject var viewModel : DashViewModel @EnvironmentObject var viewModel : DashViewModel
var body: some View { var body: some View {
@@ -43,7 +46,28 @@ struct ScheduleView: View {
HStack { HStack {
Spacer() 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() Spacer()
} }
// Time Slots with Events // Time Slots with Events
@@ -221,6 +245,7 @@ struct ScheduleView: View {
} }
.onTapGesture { .onTapGesture {
selectedDate = date selectedDate = date
viewModel.getDailySched(clubId: selectedCourtId ?? 0)
} }
.id(calendar.startOfDay(for: date)) .id(calendar.startOfDay(for: date))
} }
@@ -20,9 +20,9 @@ struct AnalyticsView: View {
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)
} }
@@ -86,14 +86,14 @@ struct PieChartCard: View {
var body: some View { var body: some View {
let chartData = data.map { 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") { CardView(title: "% made through each module") {
VStack { VStack {
HStack { HStack {
ForEach(chartData, id: \.name) { item in ForEach(chartData, id: \.name) { item in
LegendView(color: item.color, text: item.name) LegendView(color: item.color, text: item.name ?? "")
} }
.padding(.top, 5) .padding(.top, 5)
} }
@@ -111,9 +111,16 @@ struct PieChartCard: View {
} }
} }
func randomColor() -> Color { func randomColor(module: String) -> Color {
let colors: [Color] = [Colorr.pieBlue, Colorr.pieGreen, .orange, .purple, .pink, .mint] switch(module){
return colors.randomElement() ?? .blue case "Clinics":
return Colorr.pieBlue
case "Coaching":
return Colorr.greenColor
default:
return .orange
}
} }
} }
@@ -156,10 +163,10 @@ struct BarChartCard: View {
var body: some View { var body: some View {
let barData: [ModuleRevenue] = data.map { 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 Chart(barData) { data in
BarMark( BarMark(
x: .value("Day", data.day), x: .value("Day", data.day),
@@ -27,20 +27,52 @@ struct SummaryView: View {
if selectedTab == 0 { if selectedTab == 0 {
FilterView(navigationPaths: $navigationPaths, filterCount: $filterCount) 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 { ScrollView {
LazyVStack(spacing: 10) { 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(
ReservationCard(title: "Clinic reservations", hours: "\(self.viewModel.statResponse?.clinicReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.clinicReservations.first?.total_revenue ?? 0 )") title: "Court reservations",
ReservationCard(title: "Find a Buddy", hours: "\(self.viewModel.statResponse?.buddyStatistics.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.buddyStatistics.first?.total_revenue ?? 0 )") hours: String(format: "%.2f", self.viewModel.statResponse?.coachReservations?.first?.total_hours ?? 0),
ReservationCard(title: "Lesson reservation", hours: "\(self.viewModel.statResponse?.lessonReservations.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.lessonReservations.first?.total_revenue ?? 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(
ReservationCard(title: "Total profit", hours: "\(self.viewModel.statResponse?.totalProfit.first?.total_profit ?? 0 )", revenue: "$\(self.viewModel.statResponse?.totalProfit.first?.total_revenue ?? 0 )") title: "Clinic reservations",
ReservationCard(title: "Total revenue", hours: "\(self.viewModel.statResponse?.totalRevenue.first?.total_hours ?? 0 )", revenue: "$\(self.viewModel.statResponse?.totalRevenue.first?.total_revenue ?? 0 )") 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) .padding(.horizontal)
@@ -57,7 +89,7 @@ struct SummaryView: View {
if value == "FilterScreen" { if value == "FilterScreen" {
FilterScreen(navigationPaths: $navigationPaths) { startDate, endDate, startTime, endTime, filterCount in FilterScreen(navigationPaths: $navigationPaths) { startDate, endDate, startTime, endTime, filterCount in
self.filterCount = filterCount 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 = "" AppSettings.token = ""
} }
Button("Cancel", role: .cancel) { } Button("Cancel", role: .cancel) {
}
} }
} }
} }
@@ -14,7 +14,8 @@ struct FilterScreen: View {
@State private var endTime: Date = Date() @State private var endTime: Date = Date()
@State private var startDate: Date = Date() @State private var startDate: Date = Date()
@State private var endDate: Date = Date() @State private var endDate: Date = Date()
@EnvironmentObject var viewModel : DashViewModel
@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"]
@@ -33,14 +34,14 @@ struct FilterScreen: View {
DatePicker("End Date", selection: $endDate, displayedComponents: .date) DatePicker("End Date", selection: $endDate, displayedComponents: .date)
} }
Section(header: Text("Duration")) { // Section(header: Text("Duration")) {
Picker("Duration", selection: $selectedDuration) { // Picker("Duration", selection: $selectedDuration) {
ForEach(durationOptions, id: \.self) { duration in // ForEach(durationOptions, id: \.self) { duration in
Text(duration) // Text(duration)
} // }
} // }
.pickerStyle(MenuPickerStyle()) // .pickerStyle(MenuPickerStyle())
} // }
Section { Section {
Button(action: { Button(action: {
@@ -66,17 +67,17 @@ struct FilterScreen: View {
let timeFormatter = DateFormatter() let timeFormatter = DateFormatter()
timeFormatter.dateFormat = "HH:mm:ss" timeFormatter.dateFormat = "HH:mm:ss"
let startDateStr = dateFormatter.string(from: startDate) viewModel.dashStartDate = dateFormatter.string(from: startDate)
let endDateStr = dateFormatter.string(from: endDate) viewModel.dashEndDate = dateFormatter.string(from: endDate)
let startTimeStr = timeFormatter.string(from: startTime) viewModel.dashStartTime = timeFormatter.string(from: startTime)
let endTimeStr = timeFormatter.string(from: endTime) viewModel.dashEndTime = timeFormatter.string(from: endTime)
let query = """ // let query = """
start_date=\(startDateStr)&\ // start_date=\(startDateStr)&\
end_date=\(endDateStr)&\ // end_date=\(endDateStr)&\
start_time=\(startTimeStr)&\ // start_time=\(startTimeStr)&\
end_time=\(endTimeStr) // end_time=\(endTimeStr)
""" // """
// 🔢 Count how many filters are applied // 🔢 Count how many filters are applied
var filterCount = 0 var filterCount = 0
@@ -86,10 +87,10 @@ struct FilterScreen: View {
if !Calendar.current.isDate(endTime, equalTo: Date(), toGranularity: .minute) { filterCount += 1 } if !Calendar.current.isDate(endTime, equalTo: Date(), toGranularity: .minute) { filterCount += 1 }
if selectedDuration != "This Day" { filterCount += 1 } if selectedDuration != "This Day" { filterCount += 1 }
print("Generated Filter Query: \(query)") // print("Generated Filter Query: \(query)")
print("Filter count: \(filterCount)") print("Filter count: \(filterCount)")
self.filterBlock?(startDateStr, endDateStr, startTimeStr, endTimeStr, filterCount) // self.filterBlock?(startDateStr, endDateStr, startTimeStr, endTimeStr, filterCount)
self.navigationPaths.removeLast() self.navigationPaths.removeLast()
} }
} }
@@ -61,6 +61,7 @@ struct SideMenuView: View {
AppSettings.token = "" AppSettings.token = ""
presentSideMenu = false presentSideMenu = false
} }
}) })
} }
@@ -368,3 +368,13 @@ extension Date {
return formatter.string(from: self) return formatter.string(from: self)
} }
} }
extension String {
var toCommaSeparated: String {
self.trimmingCharacters(in: CharacterSet(charactersIn: "[]"))
.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespaces) }
.joined(separator: ",")
}
}
@@ -21,26 +21,29 @@ class DashViewModel: ObservableObject {
@Published var selectedReservation : ReservationList? @Published var selectedReservation : ReservationList?
@Published var players : [UserMap]? @Published var players : [UserMap]?
@Published var coaches : [PlayerList]? @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( func getStats(
startDate: String? = "2024-10-25",
endDate: String? = "2024-11-25",
startTime: String? = "17:30:20",
endTime: String? = "19:20:30",
completion: @escaping () -> ()) { completion: @escaping () -> ()) {
let endpoint = ClubStatisticsEndpoint( let endpoint = ClubStatisticsEndpoint(
startDate: startDate!, startDate: dashStartDate,
endDate: endDate!, endDate: dashEndDate,
startTime: startTime!, startTime: dashStartTime,
endTime: endTime! endTime: dashEndTime
) )
Task { Task {
do { do {
let response: ClubStatisticsResponse = try await APIService.shared.request(endpoint, responseType: ClubStatisticsResponse.self) let result = try await APIService.shared.requestStatics(endpoint)
print("Statistics fetched: \(response.model)") print("Statistics fetched: \(result?.model)")
if !response.error {
self.statResponse = response.model if !result!.error! {
self.statResponse = result?.model
} }
else { else {
@@ -49,7 +52,7 @@ class DashViewModel: ObservableObject {
print("Error fetching stats: \(error)") print("Error fetching stats: \(error)")
// if error.localizedDescription == "Token expired" { // if error.localizedDescription == "Token expired" {
sessionExpired = true sessionExpired = true
completion() completion()
// } // }
} }
} }