fixed some issues
This commit is contained in:
@@ -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;
|
||||
|
||||
-16
@@ -100,22 +100,6 @@
|
||||
landmarkType = "24">
|
||||
</BreakpointContent>
|
||||
</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
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<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? {
|
||||
guard let request = endpoint.urlRequest else {
|
||||
throw APIError.invalidURL
|
||||
|
||||
+290
-41
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
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]
|
||||
func mapping(map: Map) {
|
||||
|
||||
error <- map["error"]
|
||||
model <- map["model"]
|
||||
}
|
||||
|
||||
struct ReservationStats: Decodable {
|
||||
let total_hours: Double?
|
||||
let total_revenue: Double?
|
||||
}
|
||||
|
||||
struct RevenueHeatMap: Decodable {
|
||||
let date: String
|
||||
let total_revenue: Double
|
||||
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) {
|
||||
|
||||
}
|
||||
|
||||
struct RevenueByDateRange: Decodable {
|
||||
// Add fields if needed
|
||||
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 RevenueByModule: Decodable {
|
||||
let module: String
|
||||
let revenue: Double?
|
||||
}
|
||||
|
||||
struct ExpenseStats: Decodable {
|
||||
let total_hours: Double?
|
||||
let total_expense: Double?
|
||||
class BuddyStatistics : Mappable {
|
||||
var total_hours : String?
|
||||
var total_revenue : String?
|
||||
|
||||
required init?(map: Map) {
|
||||
|
||||
}
|
||||
|
||||
struct RevenueStats: Decodable {
|
||||
let total_hours: Double?
|
||||
let total_revenue: Double?
|
||||
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?
|
||||
}
|
||||
|
||||
struct CourtUtilizationStats: Decodable {
|
||||
let utilization_percentage: Double?
|
||||
let total_used_hours: Double?
|
||||
let total_available_hours: 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"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
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"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BarData : Mappable {
|
||||
var clinic : [Int]?
|
||||
var coach : [Double]?
|
||||
|
||||
required init?(map: Map) {
|
||||
|
||||
}
|
||||
|
||||
func mapping(map: Map) {
|
||||
|
||||
clinic <- map["clinic"]
|
||||
coach <- map["coach"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class RevenueBarChart : Mappable {
|
||||
var days : [String]?
|
||||
var data : BarData?
|
||||
|
||||
required init?(map: Map) {
|
||||
|
||||
}
|
||||
|
||||
func mapping(map: Map) {
|
||||
|
||||
days <- map["days"]
|
||||
data <- map["data"]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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"]
|
||||
}
|
||||
|
||||
}
|
||||
+3
@@ -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"]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
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)
|
||||
Text("Hours playing: 9:00 AM – 10:00 AM")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(10)
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
.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: "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")")
|
||||
|
||||
@@ -43,7 +43,7 @@ struct ScheduleFilterView: View {
|
||||
}
|
||||
.padding(.top, 10)
|
||||
} label: {
|
||||
Text("Club")
|
||||
Text("Courts")
|
||||
.font(.headline)
|
||||
}
|
||||
.padding()
|
||||
|
||||
@@ -13,34 +13,44 @@ struct PlayersTab: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
ForEach(0..<5, id: \.self) { index in
|
||||
if let players = viewModel.players {
|
||||
ForEach(players, id: \.id) { index in
|
||||
HStack(spacing: 12) {
|
||||
ProfileImageView(imageUrl: "", size: 40)
|
||||
ProfileImageView(imageUrl: index.photo, size: 40)
|
||||
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Steve Jobs")
|
||||
Text("\(index.first_name ?? "") \(index.last_name ?? "" )")
|
||||
.font(.headline)
|
||||
|
||||
if index == 0 {
|
||||
Text("Guardian sign-in: Steve Jobs")
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
// if index == 0 {
|
||||
// Text("Guardian sign-in: Steve Jobs")
|
||||
// .font(.caption)
|
||||
// .foregroundColor(.gray)
|
||||
// }
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if index == 0 {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.foregroundColor(.green)
|
||||
}
|
||||
// if index == 0 {
|
||||
// Image(systemName: "checkmark.seal.fill")
|
||||
// .foregroundColor(.green)
|
||||
// }
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemGray6))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.onAppear(){
|
||||
if let ids = viewModel.selectedReservation?.booking?.player_ids?.toCommaSeparated {
|
||||
|
||||
viewModel.getPlayers(playerIds: ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ struct ScheduleView: View {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ struct SideMenuView: View {
|
||||
AppSettings.token = ""
|
||||
presentSideMenu = false
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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: ",")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -22,25 +22,28 @@ class DashViewModel: ObservableObject {
|
||||
@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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user