updated code

This commit is contained in:
umer
2025-10-10 00:06:56 +05:00
parent 154d1882c8
commit 3ff65fe17b
13 changed files with 245 additions and 19 deletions
@@ -9,6 +9,8 @@
/* Begin PBXBuildFile section */
AC136A092DB96847009D478F /* ObjectMapper in Frameworks */ = {isa = PBXBuildFile; productRef = AC136A082DB96847009D478F /* ObjectMapper */; };
AC53DEAA2DB9476D003445AD /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = AC53DEA92DB9476D003445AD /* Alamofire */; };
AC8826F32E6F5A6700ECEF0D /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = AC8826F22E6F5A6700ECEF0D /* CropViewController */; };
AC8826F52E6F5A6700ECEF0D /* TOCropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = AC8826F42E6F5A6700ECEF0D /* TOCropViewController */; };
ACAC2DEB2D9F2C1900869E5C /* CalendarKit in Frameworks */ = {isa = PBXBuildFile; productRef = ACAC2DEA2D9F2C1900869E5C /* CalendarKit */; };
/* End PBXBuildFile section */
@@ -30,6 +32,8 @@
buildActionMask = 2147483647;
files = (
AC136A092DB96847009D478F /* ObjectMapper in Frameworks */,
AC8826F52E6F5A6700ECEF0D /* TOCropViewController in Frameworks */,
AC8826F32E6F5A6700ECEF0D /* CropViewController in Frameworks */,
ACAC2DEB2D9F2C1900869E5C /* CalendarKit in Frameworks */,
AC53DEAA2DB9476D003445AD /* Alamofire in Frameworks */,
);
@@ -77,6 +81,8 @@
ACAC2DEA2D9F2C1900869E5C /* CalendarKit */,
AC53DEA92DB9476D003445AD /* Alamofire */,
AC136A082DB96847009D478F /* ObjectMapper */,
AC8826F22E6F5A6700ECEF0D /* CropViewController */,
AC8826F42E6F5A6700ECEF0D /* TOCropViewController */,
);
productName = "Club Portal";
productReference = ACAC2DCF2D9F271300869E5C /* Club Portal.app */;
@@ -110,6 +116,7 @@
ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */,
AC53DEA82DB9476D003445AD /* XCRemoteSwiftPackageReference "Alamofire" */,
AC136A072DB96847009D478F /* XCRemoteSwiftPackageReference "ObjectMapper" */,
AC8826F12E6F5A6700ECEF0D /* XCRemoteSwiftPackageReference "TOCropViewController" */,
);
preferredProjectObjectVersion = 77;
productRefGroup = ACAC2DD02D9F271300869E5C /* Products */;
@@ -267,16 +274,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\"";
DEVELOPMENT_TEAM = BSGYUG5U29;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Club-Portal-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Club Portal";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -300,16 +309,18 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_ASSET_PATHS = "\"Club Portal/Preview Content\"";
DEVELOPMENT_TEAM = BSGYUG5U29;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Club-Portal-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "Club Portal";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -367,6 +378,14 @@
minimumVersion = 5.10.2;
};
};
AC8826F12E6F5A6700ECEF0D /* XCRemoteSwiftPackageReference "TOCropViewController" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/TimOliver/TOCropViewController";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.7.4;
};
};
ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/richardtop/CalendarKit";
@@ -388,6 +407,16 @@
package = AC53DEA82DB9476D003445AD /* XCRemoteSwiftPackageReference "Alamofire" */;
productName = Alamofire;
};
AC8826F22E6F5A6700ECEF0D /* CropViewController */ = {
isa = XCSwiftPackageProductDependency;
package = AC8826F12E6F5A6700ECEF0D /* XCRemoteSwiftPackageReference "TOCropViewController" */;
productName = CropViewController;
};
AC8826F42E6F5A6700ECEF0D /* TOCropViewController */ = {
isa = XCSwiftPackageProductDependency;
package = AC8826F12E6F5A6700ECEF0D /* XCRemoteSwiftPackageReference "TOCropViewController" */;
productName = TOCropViewController;
};
ACAC2DEA2D9F2C1900869E5C /* CalendarKit */ = {
isa = XCSwiftPackageProductDependency;
package = ACAC2DE92D9F2C1900869E5C /* XCRemoteSwiftPackageReference "CalendarKit" */;
@@ -1,5 +1,5 @@
{
"originHash" : "60390f3252b346980a2b0f96582e92fec2c37e9487d7b684f3d58a001c214046",
"originHash" : "b1b0353358422b4fa2ea090c1b3ed8788cceebbcf36fc271f832e564b41e6da4",
"pins" : [
{
"identity" : "alamofire",
@@ -27,6 +27,15 @@
"revision" : "6021c6035e83a306047348666f6400dc61445d3b",
"version" : "4.4.3"
}
},
{
"identity" : "tocropviewcontroller",
"kind" : "remoteSourceControl",
"location" : "https://github.com/TimOliver/TOCropViewController",
"state" : {
"revision" : "a634cb7cdfd580006e79a6e74e64417fe9e9783b",
"version" : "2.7.4"
}
}
],
"version" : 3
@@ -7,7 +7,7 @@
enum APIConstants {
static let baseURL = "https://courtmatchup.mkdlabs.com"
static let baseURL = "http://199.241.139.243:3048" //"https://courtmatchup.mkdlabs.com"
static let xProject = "Y291cnRtYXRjaHVwOmczbmp1OTlpZjR3enk4Z3V0bHEwYmUwZDI1Nzk1MTNsbTg="
static var commonHeaders: [String: String] {
var headers = ["Content-Type": "application/json",
@@ -35,3 +35,21 @@ struct ReservationListEndpoint: Endpoint {
var isMultipart: Bool { false }
}
// MARK: - Search reservations by user first name (contains)
struct ReservationSearchEndpoint: Endpoint {
var urlQueryItems: [URLQueryItem] = []
var path: String {
"/v4/api/records/reservation?join=booking|booking_id&join=user|user_id&join=buddy|buddy_id&join=clubs|club_id&join=clinics|clinic_id&order=id,desc&filter=courtmatchup_user.first_name,cs,\(query)"
}
var method: HTTPMethod { .get }
let query: String
var customHeaders: [String: String]? { nil }
var body: Data? { nil }
var isMultipart: Bool { false }
}
@@ -24,14 +24,19 @@ struct AvailabliltyRsp: Codable {
// MARK: - Coaches
struct Coaches: Codable {
// legacy shape
let available: [CoachesAvailable]?
let unavailable: [CoachesAvailable]?
let total: Int?
// new shape
let items: [CoachesAvailable]?
let counts: JSONAny?
let pagination: JSONAny?
}
// MARK: - CoachesAvailable
struct CoachesAvailable: Codable , Identifiable, Hashable {
let id = UUID().uuidString
let id: Int?
let coachID, userID: Int?
let bio, hourlyRate, availability, firstName: String?
let lastName, phone, email: String?
@@ -114,9 +119,14 @@ struct Sport: Codable, Identifiable, Hashable{
// MARK: - Courts
struct Courts: Codable {
// Old payload support
let available: [CourtsAvailable]?
let unavailable: [JSONAny]?
let total: Int?
// New payload support
let items: [CourtsAvailable]?
let counts: JSONAny?
let pagination: JSONAny?
}
// MARK: - CourtsAvailable
@@ -127,6 +137,9 @@ struct CourtsAvailable: Codable, Identifiable , Hashable{
let availability, sportName: String?
let surfaceName: String?
let availabilityData: AvailabilityData?
// New payload fields
let availabilitySlots: [Availability]?
let unavailabilitySlots: [JSONAny]?
enum CodingKeys: String, CodingKey {
case id, name, type, subtype
@@ -136,6 +149,8 @@ struct CourtsAvailable: Codable, Identifiable , Hashable{
case sportName = "sport_name"
case surfaceName = "surface_name"
case availabilityData
case availabilitySlots = "availability_slots"
case unavailabilitySlots = "unavailability_slots"
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
@@ -153,15 +168,53 @@ struct Range: Codable {
// MARK: - Hours
struct Hours: Codable {
let available: [String]?
let total: Int?
// New payload fields
let available: [HourSlot]? // e.g., [{start:"11:00:00", end:"11:30:00"}]
let unavailable: [UnavailableHour]?
let range: Range?
let unavailableRange: Range?
let byDate: [ByDate]?
let unavailableByDate: [ByDateUnavailable]?
// Legacy support
let total: Int?
enum CodingKeys: String, CodingKey {
case available, unavailable, range, byDate
case unavailableRange = "unavailable_range"
case unavailableByDate = "unavailable_byDate"
case total
}
}
// MARK: - HourSlot
struct HourSlot: Codable, Hashable {
let start: String?
let end: String?
}
// MARK: - UnavailableHour
struct UnavailableHour: Codable, Hashable {
let start: String?
let end: String?
let reason: String?
}
// MARK: - ByDate
struct ByDate: Codable {
let date: String?
let ranges: [HourSlot]?
let slots: [String]?
let from, until: String?
enum CodingKeys: String, CodingKey {
case date, ranges, slots, from, until
}
}
// MARK: - ByDateUnavailable
struct ByDateUnavailable: Codable {
let date: String?
let ranges: [UnavailableHour]?
let slots: [String]?
let from, until: String?
}
@@ -169,9 +222,14 @@ struct ByDate: Codable {
// MARK: - Staff
struct Staff: Codable, Identifiable, Hashable {
var id: Int?
// legacy
let available: [StaffAvailable]?
let unavailable: [JSONAny]?
let total: Int?
// new
let items: [StaffAvailable]?
let counts: JSONAny?
let pagination: JSONAny?
static func == (lhs: Staff, rhs: Staff) -> Bool {
lhs.id == rhs.id
@@ -9,7 +9,7 @@ import SwiftUI
struct AvailabilityCardView: View {
var tab: AvailabilityTab
var courts: [CourtsAvailable] = []
var hours: [String]?
var hours: [HourSlot]?
var coaches: [CoachesAvailable]?
var stsff: [StaffAvailable]?
@State var name : String = ""
@@ -44,7 +44,7 @@ struct AvailabilityCardView: View {
}
case .hours:
ForEach(hours ?? [] , id: \.self) { hour in
Text("\(hour)")
Text("\(hour.start ?? "") - \(hour.end ?? "")")
.onTapGesture {
showSheet.toggle()
}
@@ -28,10 +28,10 @@ struct AvailabilityScreen: View {
ScrollView {
AvailabilityCardView(
tab: selectedTab,
courts: viewModel.availRsp?.courts?.available ?? [] ,
courts: viewModel.availRsp?.courts?.items ?? viewModel.availRsp?.courts?.available ?? [] ,
hours: viewModel.availRsp?.hours?.available ?? [] ,
coaches: viewModel.availRsp?.coaches?.available ?? [],
stsff: viewModel.availRsp?.staff?.available ?? []
coaches: viewModel.availRsp?.coaches?.items ?? viewModel.availRsp?.coaches?.available ?? [],
stsff: viewModel.availRsp?.staff?.items ?? viewModel.availRsp?.staff?.available ?? []
)
}
@@ -14,6 +14,7 @@ struct ScheduleView: View {
@State private var selectedCourtName: String? = nil
@State private var selectedCourtId: Int? = nil
@State private var searchQuery: String = ""
@EnvironmentObject var viewModel : DashViewModel
@@ -173,8 +174,12 @@ struct ScheduleView: View {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
TextField("search player", text: .constant(""))
TextField("Search player", text: $searchQuery)
.foregroundColor(.primary)
.submitLabel(.search)
.onSubmit {
viewModel.searchDailySched(query: searchQuery)
}
}
.padding(5)
.background(Color(.white))
@@ -184,6 +189,13 @@ struct ScheduleView: View {
.stroke(Color(.systemGray4), lineWidth: 1)
)
//.padding(.horizontal)
.onChange(of: searchQuery) { newVal in
let q = newVal.trimmingCharacters(in: .whitespacesAndNewlines)
if q.isEmpty {
// restore list for selected court when clearing search
if let cid = selectedCourtId { viewModel.getDailySched(clubId: cid) }
}
}
Button(action: {
@@ -101,7 +101,7 @@ struct SummaryView: View {
}
}
// viewModel.getClubProfile()
// viewModel.getProfile()
viewModel.getProfile()
}
@@ -126,7 +126,7 @@ struct SideMenuView: View {
func ProfileHead() -> some View {
VStack(alignment: .leading) {
HStack {
ProfileImageView(imageUrl: AppSettings.photo, size: 50)
ProfileImageView(imageUrl: AppSettings.photo.isEmpty ? AppSettings.clubLogo : AppSettings.photo, size: 50)
.padding(.leading)
Spacer()
@@ -0,0 +1,75 @@
//
// PhotoPickerWithCrop.swift
// Club Portal
//
// Created by Umer Tahir on 23/05/2025.
//
//
// PhotoPickerWithCrop.swift
// Club Portal
//
// Created by Umer Tahir on 23/05/2025.
//
import SwiftUI
import PhotosUI
import TOCropViewController
struct PhotoPickerWithCrop: UIViewControllerRepresentable {
@Binding var selectedImage: UIImage?
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var config = PHPickerConfiguration()
config.filter = .images
config.selectionLimit = 1
let picker = PHPickerViewController(configuration: config)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
class Coordinator: NSObject, PHPickerViewControllerDelegate, TOCropViewControllerDelegate {
var parent: PhotoPickerWithCrop
var imageToCrop: UIImage?
init(_ parent: PhotoPickerWithCrop) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider,
provider.canLoadObject(ofClass: UIImage.self) else { return }
provider.loadObject(ofClass: UIImage.self) { [weak self] object, _ in
guard let self = self, let image = object as? UIImage else { return }
self.imageToCrop = image
DispatchQueue.main.async {
guard let root = UIApplication.shared.windows.first?.rootViewController else { return }
let cropVC = TOCropViewController(image: image)
cropVC.delegate = self
root.present(cropVC, animated: true)
}
}
}
func cropViewController(_ cropViewController: TOCropViewController, didCropTo image: UIImage, with cropRect: CGRect, angle: Int) {
cropViewController.dismiss(animated: true)
parent.selectedImage = image
}
func cropViewControllerDidCancel(_ cropViewController: TOCropViewController) {
cropViewController.dismiss(animated: true)
}
}
}
@@ -71,6 +71,20 @@ class DashViewModel: ObservableObject {
}
}
func searchDailySched(query: String){
guard !query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
let endpoint = ReservationSearchEndpoint(query: query)
Task {
do {
let result = try await APIService.shared.requestReservation(endpoint, responseType: ProfileRsp.self)
dailyReservations = result?.list ?? []
print("Search Reservations:", result?.list)
} catch {
print("Error searching reservations:", error)
}
}
}
func getAvailability(
startDate: String? = "2024-10-25",
endDate: String? = "2024-11-25",
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>