// API/APIModels.swift // ------------------- // Codable structs that map to the JSON responses from the JQC Flask API. // All API responses use the envelope: { "ok": bool, "data": {...}, "error": string? } import Foundation // ── API Envelope ────────────────────────────────────────────────────────────── struct APIResponse: Decodable { let ok: Bool let data: T? let error: String? } // ── Auth ────────────────────────────────────────────────────────────────────── struct LoginResponseData: Decodable { let accessToken: String let refreshToken: String let tokenType: String let expiresIn: Int let user: APIUser } struct RefreshResponseData: Decodable { let accessToken: String let refreshToken: String let tokenType: String let expiresIn: Int } struct MeResponseData: Decodable { let user: APIUser } struct APIUser: Decodable { let id: Int let username: String let email: String let role: String let createdAt: String? // Computed display name: full name if present, otherwise username. // The server's /auth/me endpoint returns username; display_name would // require a separate field. For Phase A we use username as the display name // and can extend later. var displayName: String { username } } // ── Facilities ──────────────────────────────────────────────────────────────── struct FacilitiesResponseData: Decodable { let facilities: [APIFacility] let count: Int } struct AreasResponseData: Decodable { let facilityId: Int let areas: [APIArea] let count: Int } struct APIFacility: Decodable, Identifiable { let id: Int let name: String let address: String let contactPerson: String let contactPhone: String let projectId: Int? let projectName: String? let isActive: Bool } struct APIArea: Decodable, Identifiable { let id: Int let facilityId: Int let name: String let areaType: String } // ── Templates ───────────────────────────────────────────────────────────────── struct TemplatesResponseData: Decodable { let templates: [APITemplateSummary] let count: Int } struct TemplateDetailResponseData: Decodable { let template: APITemplate } struct APITemplateSummary: Decodable, Identifiable { let id: Int let name: String let description: String let frequency: String } struct APITemplate: Decodable, Identifiable { let id: Int let name: String let description: String let frequency: String let formSchema: [[String: AnyDecodable]] // Dynamic JSON fields } // ── AnyDecodable helper ─────────────────────────────────────────────────────── // Allows decoding JSON values of unknown type (String, Int, Bool, Array, Dict) struct AnyDecodable: Decodable { let value: Any init(_ value: Any) { self.value = value } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let bool = try? container.decode(Bool.self) { value = bool; return } if let int = try? container.decode(Int.self) { value = int; return } if let dbl = try? container.decode(Double.self) { value = dbl; return } if let str = try? container.decode(String.self) { value = str; return } if let arr = try? container.decode([AnyDecodable].self) { value = arr.map(\.value); return } if let dict = try? container.decode([String: AnyDecodable].self) { value = dict.mapValues(\.value); return } value = NSNull() } }